# spring-cloud-alibaba-notes **Repository Path**: baoRoot/spring-cloud-alibaba-notes ## Basic Information - **Project Name**: spring-cloud-alibaba-notes - **Description**: 学习spring cloud alibaba微服务笔记 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-06-09 - **Last Updated**: 2023-06-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: spring-cloud-alibab, Java ## README # spring-cloud-alibaba-notes 学习spring-cloud-alibaba笔记 ## 一、版本 Spring-cloud-Alibaba: 2.2.9.RELEASE Spring-boot: 2.3.12.RELEASE spring-cloud: Hoxton.SR12 Nacos: 2.1.0 Gateway: 2.2.1.RELEASE Sentinel: 1.8.5 RocketMQ: 4.9.4 Seata: 1.5.2 Dubbo: 2.7.13 ## 二、提交笔记 ### 1、开始 创建两个模块(spring-cloud-order/spring-cloud-stock),两个模块之间进行调用,模拟了微服务模块之间的互相调用,但是 发现,我们在A服务中调用B服务时,在A服务中需要写B模块的接口地址,如果有很多个微服务要调用,接口地址就要写很多,并且不知道 端口是多少,万一另外一个模块的接口地址更换,则需要慢慢的去更改接口地址很麻烦,所以我们的解决方案就是使用注册中心Nacos进行管理; ### 2、Nacos注册中心 主要将每一个微服务进行一个注册,在配置类中添加上负载均衡注解 配置类 ```java @Configuration public class RestTemplateConfig { @Bean @LoadBalanced // 负载均衡 Ribbon public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){ return restTemplateBuilder.build(); } } ``` ```yml spring: application: name: spring-cloud-stock-nacos cloud: nacos: server-addr: 127.0.0.1:8848 # 配置nacos地址,默认会将上面的名称作为服务名进行注册 ``` 1、单机压测 ======= 10线程30次循环两个模块 ======= === 总耗时 === 平均接口耗时 === === 105.192 === 42 === ======= 10线程30次循环一个模块 ======= === 总耗时 === 平均接口耗时 === === 114.202 === 60 === ### 3、OpenFeign使用 Feign是一个Netflix开发的声明式、模块化的HTTP客户端,他可以帮助我们更加快捷、优雅的调用HTTP API,Feign 支持多种注解列如Feign自带的注解或者JAX-ES注解等,而OpenFeign是对Feign进行增强,支持Spring MVC,另外还整 合了Ribbon和Nacos,从而使得OpenFeign更加方便,OpenFeign是spring cloud中的,所以需要引入spring cloud 1、在调用的模块中已service的形式将feign进行一个封装,已service的形式注册controller层进行使用,此时重点就是feign中 如何设置 ```java @FeignClient( name = "spring-cloud-stock-nacos", // 这里的名称只的是调用方法处理的实例模块名称,也就是在nacos注册中心注册的微服务名称 path = "/stock") // 在目标模块中的请求 根地址 也就是相当于@RequestMapping("/stock")注解, public interface StockFeignService { // 这里的请求方式,请求参数,请求方法名称、返回值类型和目标模块中需要调用的controller一致 @GetMapping("/redact") String redact(); /** 例如:我的目标模块中的controller是以下代码,然后以上代码是openFeign的书写方式 @RestController @RequestMapping("/stock") public class StockController { @Value("${server.port}") private String port; @GetMapping("/redact") public String redact(){ return "扣减 OpenFeign" + port; } } */ } ``` 注意: 在A模块中的controller注入StockFeignService报错,需要在启动类上加上@EnableFeignClients注解; ### 4、OpenFeign配置日志 1、定义一个全局配置类,指定日志级别 ```java @Configuration public class OpenFeignConfig{ @Bean public Logger.Level feignLoggerLevel(){ return Logger.Level.FULL; } } ``` 日志等级分为四种: + NONE【性能最佳,适用于生产】:不记录任何日志(默认值) + BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态码以及执行时间 + HEADERS:记录BASIC级别的基础上,记录请求和响应的header + FULL【适用于开发环境】:记录请求和响应的header、body和元数据 2、局部配置,让调用的微服务生效,在@FeignClient注解中指定使用的配置类,注意在日志类中不能添加@Configuration注解,因为是局部 ```java @FeignClient( name = "spring-cloud-stock-nacos", path = "/stock", configuration = OpenFeignConfig.class // 指定日志类,注意在OpenFeignConfig类上不能添加@Configuration注解 ) public interface StockFeignService { @GetMapping("/redact") String redact(); } ``` 注意: ```yml logging: level: org.xczy.order.feign: debug ``` ### 5、OpenFeign配置超时时间 1、通过Options配置,Options第一个参数是连接的超时时间(ms),默认2s,第二个参数是请求处理的超时时间(ms),默认5s 全局配置 ```java @Configuration public class OpenFeignConfig { @Bean public Request.Options options() { return new Request.Options(5000, 10000); } } ``` yml中配置 ```yml feign: client: config: mall-order: # 对应微服务名称 connectTimeout: 5000 # 连接的超时时间 readTimeout: 10000 # 请求处理的超时时间 ``` ### 6、自定义拦截器(服务调服务的拦截器不是MVC的拦截器) ```java // 拦截器类 public class FeignAuthQuestInterceptor implements QuesrtInterceptor { @Override public void apply(RequestTemplate requestTemplate) { // 业务逻辑 例如是否能访问该服务,校验数据信息等 String token = UUID.randomUUID().toString(); template.header("token", token); } } // OpenFeign配置类 @Configuration public class OpenFeignConfig { @Bean // 将拦截器配置在这里 public FeignAuthQuestInterceptor feignAuthQuestInterceptor(){ return new FeignAuthQuestInterceptor(); } } ``` ### 7、Nacos配置中心 作用:维护性、时效性、安全性 优点:可以动态感知nacos配置中心数据发生变化 1、启动nacos服务,在浏览器中登录nacos,然后在配置管理的配置列表中添加一个配置列表 ```properties user.name=yx user.age=18 ``` 2、然后在项目中配置一个spring boot启动类并引入依赖和nacos配置依赖 ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config org.springframework.boot spring-boot-starter-web 2.3.12.RELEASE ``` 3、在resources下新建一个bootstrap.yml文件 ```yml spring: application: name: spring-cloud-order # 去注册中心 读取的注册文件的dataId(就是需要读取哪个配置文件),一般名称和模块名一致 cloud: nacos: config: server-addr: 127.0.0.1:8848 # nacos地址 username: nacos password: nacos ``` 3、在启动项中获取nacos中得到配置信心 ```java @SpringBootApplication public class NacosConfigApplication { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(NacosConfigApplication.class, args); String name = run.getEnvironment().getProperty("user.name"); String age = run.getEnvironment().getProperty("user.age"); System.out.println("名字为:" + name); System.out.println("年龄为:" + age); } } ``` 4、注意: a.在nacos注册中心可以配置六种类型的文件(test/json/xml/yaml/html/properties),默认获取的是properties类型的 文件,如果在nacos配置中心是使用yaml配置的数据,则需要在bootstrap.yml中配置以下信息: ```yml spring: cloud: nacos: config: file-extension: yml # 配置该服务从nacos配置中心获取的文件类型,默认是properties文件类型 ``` 这样就可以从yml文件类型的文件中获取数据信心 b.使用@Value注解无法动态的获取到配置中心的值,可以使用@RefreshScope注解 ```java @RestController @RequestMapping("/stock") @RefreshScope public class StockController { @Value("${user.name}") private String port; // nacos配置中心的数据发生变化时@Value注解无法动态的感知数据变化,配合@RefreshScope即可 } ``` ### 8、Gateway 网关作为流量入口,常用的功能包括路由转发、权限校验、限流等,核心概念:路由、断言、过滤器 1、引入依赖,注意:会和spring-webmvc的依赖冲突,需要排除spring-webmvc ```xml org.springframework.cloud spring-cloud-starter-gateway ``` 2、编写yml文件 ```yml server: port: 8080 spring: application: name: spring-cloud-gateway cloud: # gateway的配置 gateway: # 路由规则 routes: - id: order_route # 路由的唯一标识 uri: http://localhost:7002 # 需要转发的地址,注意是 i 不是 l predicates: # 断言规则 用于路由规则的匹配 - Path=/order/** # http://localhost:8080/order/stock/redact 转发到下面的路由 # http://localhost:7001/order/stock/redact filters: - StripPrefix=1 # 过滤器 转发之前去掉第一层路径,得到下面路径 # http://localhost:7001/stock/redact ``` ### 9、gateway整合Nacos 1、引入依赖 ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery ``` 2、编写yml配置文件 ```yml spring: application: name: spring-cloud-gateway-nacos cloud: # nacos 配置 nacos: discovery: server-addr: 127.0.0.1:8848 username: nacos password: nacos # gateway的配置 gateway: # 路由规则 routes: - id: product-route uri: lb://service-product # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略 predicates: - Path=/order/** # 注意Path首字母大写 filters: - StripPrefix=1 ``` 3、简写:去掉关于路由的配置,自动寻找服务 ```yml server: port: 8081 spring: application: name: spring-cloud-gateway-nacos cloud: # nacos 配置 nacos: discovery: server-addr: 127.0.0.1:8848 username: nacos password: nacos # gateway的配置 gateway: discovery: locator: enabled: true # 是否启动自动识别nacos服务 ``` ### 10、过滤器工厂GatewayFilter(局部过滤器) gateway内置了很多过滤器工厂,例如剔除响应头、添加去除参数等 | 过滤器名称 | 作用 | 参数 | | :---- | :---- | :---- | | AddRequestHeader | 为原始请求添加Header | Header的名称及值 | | AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 | | AddResponseHeader | 为原始响应添加Header | Header的名称及值 | | DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名称及去重策略 | | Hystrix | 为路由引入Hystrix的断路器保护 | HystrixCommand的名称 | | FallbackHeaders | 为fallbackUri的请求头中添加具体的异常信息 | Header的名称 | | PrefixPath | 为原始请求路径添加前缀 | 前缀路径 | | PreserveHostHeader | 为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host | 无 | | RequestRateLimiter | 用于对请求限流,限流算法为令牌桶 | keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus | | RedirectTo | 将原始请求重定向到指定的URL | http状态码及重定向的url | | RemoveHopByHopHeadersFilter| 为原始请求删除IETF组织规定的一系列Header | 默认就会启用,可以通过配置指定仅删除哪些Header | | RemoveRequestHeader | 为原始请求删除某个Header | Header名称 | | RemoveResponseHeader | 为原始响应删除某个Header | Header名称 | | RewritePath | 重写原始的请求路径 | 原始路径正则表达式以及重写后路径的正则表达式 | | RewriteResponseHeader | 重写原始响应中的某个Header | Header名称,值的正则表达式,重写后的值 | | SaveSession | 在转发请求之前,强制执行WebSession::save操作 | 无 | | secureHeaders | 为原始响应添加一系列起安全作用的响应头 | 无,支持修改这些安全响应头的值 | | SetPath | 修改原始的请求路径 | 修改后的路径 | | SetResponseHeader | 修改原始响应中某个Header的值 | Header名称,修改后的值 | | SetStatus | 修改原始响应的状态码 | HTTP 状态码,可以是数字,也可以是字符串 | | StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的路径的数量 | | Retry | 针对不同的响应进行重试 | retries、statuses、methods、series | | RequestSize | 设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回 413 Payload Too Large | 请求包大小,单位为字节,默认值为5M | | ModifyRequestBody | 在转发请求之前修改原始请求体内容 | 修改后的请求体内容 | | ModifyResponseBody | 修改原始响应体的内容 | 修改后的响应体内容 | | Default | 为所有路由添加过滤器 | 过滤器工厂名称及值 | 使用示例: ```yml spring: application: name: spring-cloud-gateway-nacos cloud: # nacos 配置 nacos: discovery: server-addr: 127.0.0.1:8848 username: nacos password: nacos # gateway的配置 gateway: # 路由规则 routes: - id: product-route uri: lb://service-product # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略 predicates: - Path=/order/** # 注意Path首字母大写 filters: - StripPrefix=1 - AddRequestHeader=X-Request-color, red # 在请求头中添加一个X-Request-color属性,值为red,在下游可以使用@RequestHeader注解去获取进行使用 ``` ### 11、全局过滤器 1、Forward Routing Filter 2、ReactiveLoadBalancerClientFilter 3、Netty Routing Filter 4、Netty Write Response Filter 5、RouteToRequestUrl Filter 6、Websocket Routing Filter 7、Gateway Metrics Filter 局部过滤器和全局过滤器的区别: 局部:局部过滤器只针对某个路由,需要在路由中进行配置 全局:针对所有路由请求,一旦定义就会执行 注:详细笔记在语雀文档中记录(www.yuque.com/baoroot) ### 12、Gateway整合Sentinel流控降级 1、引入依赖 ```xml com.alibaba.cloud spring-cloud-starter-alibaba-sentinel ``` 2、在项目配置文件中加上sentinel配置 ```yml spring: cloud: sentinel: transport: port: 8719 #sentinel通信接口 dashboard: 192.168.161.3:8774 #sentinel控制台服务地址 ``` 3、配置流控规则,配置接口流控规则,我们这里QPS设置为1,然后在接口测试响应码为429则实现Sentinel限流 4、如果想针对限流进行定制化信息响应,可以在WebFluxCallbackManager中注册回调自定义的BlockHandler ```java public class MySentinelBlockHandler implements BlockRequestHandler { @Override public Mono handleRequest(ServerWebExchange exchange, Throwable t) { ErrorResult errorResult = new MySentinelBlockHandler.ErrorResult( HttpStatus.TOO_MANY_REQUESTS.value(), "系统繁忙,请稍后再试!"); // JSON result by default. return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) .contentType(MediaType.APPLICATION_JSON_UTF8) .body(fromObject(errorResult)); } private static class ErrorResult { private final int code; private final String message; ErrorResult(int code, String message) { this.code = code; this.message = message; } public int getCode() { return code; } public String getMessage() { return message; } } } /** * 响应的结果为 * { * code: 429, * message: "系统繁忙,稍后再试!" * } */ ```