springcloud
1. 创建springcloud微服务项目
最外层(项目名)->控制整体的springboot和springcloud、springcloudalibaba版本
- e.g:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <!-- 元数据--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.4</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cloud.gm-aaa</groupId> <artifactId>cloud-demo</artifactId> <version>0.0.1</version> <packaging>pom</packaging> <name>cloud-demo</name> <description>cloud-demo</description> <modules> <module>services</module> <module>model</module> </modules> <!-- 版本管理--> <properties> <java.version>17</java.version> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <spring.boot.version>3.3.4</spring.boot.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.cloud.version>2023.0.3</spring.cloud.version> <spring.cloud.alibaba.version>2023.0.3.2</spring.cloud.alibaba.version> </properties> <!-- 依赖管理--> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring.cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring.cloud.alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <!-- 插件--> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
* 第二层->控制各个微服务组件的数据库实体类bean和公用依赖
* e.g
* ```properties
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi: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>cloud.gm-aaa</groupId>
<artifactId>cloud-demo</artifactId>
<version>0.0.1</version>
</parent>
<artifactId>services</artifactId>
<packaging>pom</packaging>
<modules>
<module>service-product</module>
<module>service-order</module>
</modules>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 服务发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- RPC-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
</dependency>
<!-- model-->
<dependency>
<groupId>cloud.gm-aaa</groupId>
<artifactId>model</artifactId>
<version>0.0.1</version>
</dependency>
<!-- 负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- 配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>
</project>
第三层->控制各自微服务组件独有的依赖,也是写代码的地方
整体tree
├─.idea │ └─.cache │ └─.Apifox_Helper ├─model │ ├─src │ │ ├─main │ │ │ ├─java │ │ │ │ └─cloud │ │ │ │ └─gmaaa │ │ │ │ └─main │ │ │ │ └─bean │ │ │ │ ├─order │ │ │ │ └─product │ │ │ └─resources │ │ └─test │ │ └─java │ └─target │ ├─classes │ │ └─cloud │ │ └─gmaaa │ │ └─main │ │ └─bean │ │ ├─order │ │ └─product │ └─generated-sources │ └─annotations └─services ├─service-order │ ├─src │ │ ├─main │ │ │ ├─java │ │ │ │ └─cloud │ │ │ │ └─gmaaa │ │ │ │ └─main │ │ │ │ ├─config │ │ │ │ ├─controller │ │ │ │ ├─properties │ │ │ │ └─service │ │ │ │ └─impl │ │ │ └─resources │ │ └─test │ │ └─java │ └─target │ ├─classes │ │ └─cloud │ │ └─gmaaa │ │ └─main │ │ ├─config │ │ ├─controller │ │ ├─properties │ │ └─service │ │ └─impl │ └─generated-sources │ └─annotations └─service-product ├─src │ ├─main │ │ ├─java │ │ │ └─cloud │ │ │ └─gmaaa │ │ │ └─main │ │ │ ├─config │ │ │ ├─controller │ │ │ └─service │ │ │ └─impl │ │ └─resources │ └─test │ └─java │ └─cloud │ └─gmaaa │ └─main └─target ├─classes │ └─cloud │ └─gmaaa │ └─main │ ├─config │ ├─controller │ └─service │ └─impl ├─generated-sources │ └─annotations ├─generated-test-sources │ └─test-annotations └─test-classes └─cloud └─gmaaa └─main
## 2. nacos组件
---
### nacos服务发现(注册中心)
#### 实现:
1. maven坐标
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
服务发现配置
spring.cloud.nacos.server-addr=<nacos服务ip>:<nacos服务端口>
启动服务后,在nacos中就能看到服务。
spring.application.name
就是服务名,默认分组DEFAULT_GROUP
常用API:
远程调用
在主启动类上需要添加
@EnableDiscoveryClient
注解,代表开启服务发现功能当请求打到order时,order需要向product发起http请求以查到商品数据生成订单。此时借助spring的
RestTemplate
类的T getForObject(url, T.class)
方法发起请求,得到T
对象e.g:
public RestTemplate restTemplate() {
return new RestTemplate();
}private Product getProductById(Long productId) {
//方式1:获取商品服务所在服务器IP:端口
// List<ServiceInstance> productServices = discoveryClient.getInstances("service-product");
// ServiceInstance instance = productServices.get(0);
//方式2:负载均衡调用
// ServiceInstance serviceInstance = loadBalancerClient.choose("service-product");
// String url="http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/product/"+productId;
//方式3:基于注解的负载均衡
String url="http://service-product/product/"+productId;
// log.info("url:{}", url);
//给远程发送请求
return restTemplate.getForObject(url, Product.class);
}负载均衡
当一个服务具有多个实例时,需要分摊请求到各个实例上,实现服务的高可用时,需要启用
loadbalancer
以达到负载均衡负载均衡的默认策略是
轮询
当启用负载均衡后,第一次请求会向
nacos
发起请求以获取可用实例IP+端口,这些数据会被缓存在本地,即使之后nacos
服务下线,只要其他需要调用的微服务实例未下线也依然可用e.g:
//加上loadbalanced注解后,url的‘ip:port’可以替换为服务名,使用restTemplate发起的请求默认使用轮询
public RestTemplate restTemplate() {
return new RestTemplate();
}private Product getProductById(Long productId) {
//方式1:获取商品服务所在服务器IP:端口
// List<ServiceInstance> productServices = discoveryClient.getInstances("service-product");
// ServiceInstance instance = productServices.get(0);
//方式2:负载均衡调用
// ServiceInstance serviceInstance = loadBalancerClient.choose("service-product");
// String url="http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/product/"+productId;
//方式3:基于注解的负载均衡
String url="http://service-product/product/"+productId;
// log.info("url:{}", url);
//给远程发送请求
return restTemplate.getForObject(url, Product.class);
}
nacos配置中心(配置中心)
实现:
maven坐标
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>配置中心在微服务中的配置
- 导入配置中心依赖后,默认会开启
配置检查
,如果在nacos的配置中不存在对应配置,则将会导致微服务项目启动失败,解决方案: spring.cloud.nacos.config.import-check.enabled=false #关闭nacos配置中心导入配置检查,实现无需在nacos创建dataID
* 在微服务配置中导入nacos的配置
* ```properties
spring.config.import=nacos:<dataId数据集名字> #e.g nacos:service-order.yml,common:common.yml
- 导入配置中心依赖后,默认会开启
读取nacos中的配置和自动刷新
- 读取时,通过get方法获取到对应的值
@Component @ConfigurationProperties(prefix = "order") //指定配置中心dataId的前缀 @Data public class OrderProperties { String timeout; String autoConfirm; //nacos中的羊肉串写法映射为驼峰命名 }
* 具体实现: * ```Java @Bean ApplicationRunner runner(NacosConfigManager nacosConfigManager) { return args -> { ConfigService configService = nacosConfigManager.getConfigService(); // 通过configservice的addllistener配置监听的dataID,组 configService.addListener("service-order.yml", "DEFAULT_GROUP", new Listener() { @Override public Executor getExecutor() { //getExecutor方法用于分配线程池 return Executors.newFixedThreadPool(4); } @Override public void receiveConfigInfo(String configInfo) { //receiveConfigInfo方法用于获取到修改后的配置 System.out.println("-----config-----"+configInfo+"-----------------------"); } }); }; }
#### 常用API:
1. 配置监听nacos的配置文件,实现当配置变化时,以邮件或其他方式通知
*
* 借助spring提供的`ApplicationRunner`和`nacosConfigManager`API实现
* 它是在spring项目启动时会执行的一次性任务
* 它是函数式接口,源码如下:
* ```Java
@FunctionalInterface
public interface ApplicationRunner extends Runner {
void run(ApplicationArguments args) throws Exception;
}
配置好bean之后,微服务项目启动时便会监听指定dataID
2. 当本地和nacos的配置重复时,会应用哪个配置?
nacos的数据集中的配置会生效,因为nacos的设计初衷就是集中配置以方便管理。
优先级比较:**nacos配置>本地配置**
低优先级的配置会被丢弃,高优先级的配置会作为项目的环境变量启动;优先级规则:`先导入优先,外部优先`
* **如何确定配置的优先级?**
* **先导入优先:** 指的是配置加载的顺序。一般来说,配置加载的顺序会影响最终生效的配置。如果本地配置在 Nacos 配置之后加载,那么 Nacos 配置将会覆盖本地配置。 具体加载顺序取决于你使用的配置管理框架(例如 Spring Cloud Alibaba Nacos Config)。通常,框架会提供配置加载顺序的控制方式。
* **外部优先:** 指的是外部配置源(例如 Nacos)通常比应用程序内部的配置(例如本地配置文件)具有更高的优先级。这是因为外部配置更容易管理和更新,并且可以集中控制多个应用程序的配置。
更具体地说,以下是通常的优先级顺序(从低到高):
* **默认配置:** 代码中硬编码的默认值。
* **本地配置文件:** 例如 `application.properties` 或 `application.yml`。
* **命令行参数:** 通过命令行传递给应用程序的参数。
* **环境变量:** 操作系统环境变量。
* **Nacos 配置:** 从 Nacos 服务器获取的配置。
* **Spring Cloud Config Server (如果使用):** 如果应用集成了Spring Cloud Config Server, 它的优先级一般高于Nacos,但可以通过配置调整.
3. 数据隔离
* 当项目具有多个环境时(如:开发、测试、生产)时,用以区分不同环境的叫做命名空间(namespace),它需要被提前创建
* 当项目不同的微服务具有不同的配置时,使用组来进行区分
e.g:
```yml
spring:
application:
name: service-order
profiles:
active: dev #激活哪个环境
cloud:
nacos:
server-addr: 192.168.1.200:8848
config:
import-check:
enabled: false
namespace: ${spring.profiles.active:dev} #默认namespace是dev,否则从spring.profiles.active取值
#三种不同环境的配置
---
spring:
config:
activate:
on-profile: dev
import:
- nacos:service-order.yml?group=order
---
spring:
config:
activate:
on-profile: test
import:
- nacos:service-order.yml?group=order
---
spring:
config:
activate:
on-profile: prod
import:
- nacos:service-order.yml?group=order
openfeign远程调用
maven坐标
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>配置
#配置openfeign的日志(配置类中也需要创建一个bean)
logging:
level:
cloud.gmaaa.main.feign: debug//配置openfeign的日志(yml配置中也需要指明)
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
实现:
主启动类上需要添加@EnableFeignClients
注解,代表开启openfeign远程调用,对于微服务的多个实例,openfeign会默认轮询地调用API实现负载均衡
//发送远程调用的客户端 |
超时控制:
超时控制分为是①连接超时(connectTimeout)②读取超时(readTimeout)
feign客户端在远程调用时,会先向服务端发起连接,然后才会发送请求,服务端接收请求后返回数据
①在向服务端发起连接时失败由connectTimeout控制,在失败后返回错误信息,默认10s
②在发送请求后,服务端总是未返回数据由readTimeout控制,在失败后返回错误信息,默认60s
重试机制:
控制openfeign在调用失败后,重新发起请求的机制
openfeign的默认策略为
NEVER_RETRY
从不重试。openfeign的默认重试器为
重试间隔100ms
,最大重试次数5次
,最大间隔时间1s
。当第一次请求失败时,假如重试间隔为100ms
,那么第二次和第一次重试的间隔为100ms
,第三次和第二次的重试间隔为100ms*1.5
,第四次和第三次的重试间隔为100ms*1.5*1.5
,以此类推。自定义重试器
spring:
cloud:
openfeign:
client:
config:
default: #feign客户端默认配置
logger-level: full
connect-timeout: 3000 #单位是ms
read-timeout: 5000
order-feignProductClient:
logger-level: full
connect-timeout: 3000 #单位是ms
read-timeout: 5000
retryer: feign.Retryer.Default #配置重试机制
request-interceptors:
- cloud.gmaaa.main.interceptor.OrderRequestInterceptor #配置拦截器//配置openfeign重试器
Retryer feignRetryer() {
return new Retryer.Default(50L, 1L, 3); //重试间隔50ms,最大间隔时间1s,最大重试次数3次
}
拦截器机制:
请求拦截器:
用于在feign客户端发起请求时,拦截器可以拦截请求实现添加请求头、请求参数等信息的功能
public class OrderRequestInterceptor implements RequestInterceptor {
/**
* 请求拦截器
* @param requestTemplate 请求模板
*/
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("X-token", UUID.randomUUID().toString());
}
}spring:
cloud:
openfeign:
client:
config:
default: #feign客户端默认配置
logger-level: full
connect-timeout: 3000 #单位是ms
read-timeout: 5000
order-feignProductClient:
logger-level: full
connect-timeout: 3000 #单位是ms
read-timeout: 5000
retryer: feign.Retryer.Default #配置重试机制
request-interceptors:
- cloud.gmaaa.main.interceptor.OrderRequestInterceptor #配置拦截器
响应拦截器:
-
fallback机制:
兜底返回,需要搭配
sentinel
实现。fallback函数不一定会被调用,只有当远程调用失败时才会调用。其返回的数据可以是默认数据
、缓存数据
、假数据
//发送远程调用的客户端
public interface FeignProductClient {
//发送GET请求
Product getProductById(; Long productId)
}
public class FeignProductClientFallBack implements FeignProductClient {
public Product getProductById(Long productId) {
Product product = new Product();
product.setId(0L);
product.setName("未知商品");
product.setPrice(new BigDecimal("0"));
product.setNum(0);
return product;
}
}feign:
sentinel:
enabled: true # 开启openfeign的fallback
sentinel流量控制与熔断
在sentinel中,所有
web接口
均为资源(包括openfeign的远程调用)使用
@SentinelResource
注解声明为sentinel的资源
流控规则:
流控规则是控制资源请求的流量
QPS
=每秒最大通过的请求数