vue-java-tutorials/cloud-demo/ReadMe.md

581 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Nacos注册中心
## 注册中心
### 服务发现
发现服务信息。
```java
@SpringBootTest()
public class DiscoveryTest {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private NacosDiscoveryClient nacosDiscoveryClient;
@Test
void discoveryClientTest() {
for (String service : discoveryClient.getServices()) {
System.out.println(service);
for (ServiceInstance instance : discoveryClient.getInstances(service)) {
System.out.println("IP地址" + instance.getHost());
System.out.println("端口号" + instance.getPort());
}
}
System.out.println("----------------------------------------------");
// 两个方式一样DiscoveryClient 是 Spring自带的 NacosDiscoveryClient是 Nacos
for (String service : nacosDiscoveryClient.getServices()) {
System.out.println(service);
for (ServiceInstance instance : nacosDiscoveryClient.getInstances(service)) {
System.out.println("IP地址" + instance.getHost());
System.out.println("端口号" + instance.getPort());
}
}
}
}
```
### 远程调用
订单模块调用远程商品模块使用了nacos可以使用`RestTemplate`,其中`RestTemplate`是线程安全的,只要注册一次全局都是可以使用。
**RestTemplate源码**
继承了`InterceptingHttpAccessor`,在`InterceptingHttpAccessor`中,使用了单例模式。
```java
public ClientHttpRequestFactory getRequestFactory() {
List<ClientHttpRequestInterceptor> interceptors = this.getInterceptors();
if (!CollectionUtils.isEmpty(interceptors)) {
ClientHttpRequestFactory factory = this.interceptingRequestFactory;
if (factory == null) {
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
this.interceptingRequestFactory = factory;
}
return factory;
} else {
return super.getRequestFactory();
}
}
```
#### 实现远程调用
##### 普通方式调用
注册`RestTemplate`
```java
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
```
如果我们的服务启动了多个,在下面代码中即使一个服务宕机也可以做到远程调用。
```java
private Product getProductFromRemote(Long productId) {
// 获取商品服务所有及其的 IP+port
List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
ServiceInstance instance = instances.get(0);
// 远程URL
String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/api/product/" + productId;
// 2. 远程发送请求
log.info("远程调用:{}", url);
return restTemplate.getForObject(url, Product.class);
}
```
##### 负载均衡调用
注册`RestTemplate`
```java
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
```
使用负载均衡`LoadBalancerClient`,通过负载均衡算法动态调用远程服务。
```java
/**
* 远程调用商品模块 --- 负载均衡
*
* @param productId 商品id
* @return 商品对象
*/
private Product getProductFromRemoteWithLoadBalancer(Long productId) {
// 1. 获取商品服务所有及其的 IP+port
ServiceInstance instance = loadBalancerClient.choose("service-product");
// 远程URL
String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/api/product/" + productId;
// 2. 远程发送请求
log.info("负载均衡远程调用:{}", url);
return restTemplate.getForObject(url, Product.class);
}
```
##### 负载均衡注解调用
> [!TIP]
>
> 如果远程注册中心宕机是否可以调用?
>
> 调用过:远程调用不在依赖注册中心,可以通过。
>
> 没调用过:第一次发起远程调用;不能通过。
在`RestTemplate`上加上`@LoadBalanced`注解使用负载均衡。
```java
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
```
在实际的调用中并不需要再显式调用将URL替换成服务名称即可。
```java
/**
* 远程调用商品模块 --- 负载均衡注解调用
*
* @param productId 商品id
* @return 商品对象
*/
private Product getProductFromRemoteWithLoadBalancerAnnotation(Long productId) {
// 远程URL实现动态替换
String url = "http://service-product/api/product/" + productId;
// 远程发送请求
log.info("负载均衡注解调用:{}", url);
return restTemplate.getForObject(url, Product.class);
}
```
## 按需加载
### 数据隔离架构
![数据隔离架构图](./images/image-20250526160251551.png)
### 命名空间管理
#### 创建命名空间
> [!TIP]
> 预配置的命名空间示例可在项目 `samples/namespace-config` 目录下找到。
建议创建以下标准命名空间:
- `dev` - 开发环境
- `test` - 测试环境
- `prod` - 生产环境
**操作步骤**
1. 进入Nacos控制台命名空间管理
2. 点击"新建命名空间"
3. 填写命名空间信息ID和名称
![创建命名空间界面](./images/image-20250526160457765.png)
![命名空间列表](./images/image-20250526160718723.png)
#### 配置管理
**开发环境配置示例**
1. 基础配置:
```yaml
order:
timeout: 1min
auto-confirm: 1h
```
2. 数据库配置:
```yaml
order:
db-url: jdbc:mysql://dev-db:3306/order_dev
```
![配置管理界面](./images/image-20250526160842062.png)
#### 命名空间克隆
通过克隆功能快速创建相似环境的命名空间:
![命名空间克隆界面](./images/image-20250526161104319.png)
### 动态环境配置
#### Spring Boot 配置方案
**基础配置**
```yaml
server:
port: 8000
spring:
application:
name: service-order
profiles:
active: dev
cloud:
nacos:
server-addr: ${NACOS_HOST:192.168.95.135}:8848
config:
namespace: ${spring.profiles.active:dev} # 动态匹配当前profile
group: DEFAULT_GROUP
```
**多环境配置加载**
```yaml
spring:
config:
import:
- optional:nacos:service-order.yml
- optional:nacos:common.yml?group=order
- optional:nacos:database.yml?group=order
```
> [!NOTE]
> 使用`optional:`前缀可避免配置不存在时启动失败
### 配置读取实现
**配置类**
```java
@Configuration
@ConfigurationProperties(prefix = "order")
@Getter
@Setter
public class OrderProperties {
private String timeout;
private String autoConfirm;
private String dbUrl; // 自动映射db-url
}
```
**REST接口**
```java
@RestController
@RequestMapping("/api/order")
@RequiredArgsConstructor
public class OrderController {
private final OrderProperties orderProperties;
@GetMapping("/config")
public Map<String, String> getConfig() {
return Map.of(
"timeout", orderProperties.getTimeout(),
"autoConfirm", orderProperties.getAutoConfirm(),
"dbUrl", orderProperties.getDbUrl()
);
}
}
```
### 按需加载策略
#### 多环境差异化配置
```yaml
spring:
profiles:
active: prod # 可通过启动参数覆盖
---
# 生产环境配置
spring:
config:
import:
- nacos:service-order-prod.yml
- nacos:common-prod.yml?group=order
activate:
on-profile: prod
---
# 开发环境配置
spring:
config:
import:
- nacos:service-order-dev.yml
- nacos:database-dev.yml?group=order
activate:
on-profile: dev
---
# 测试环境配置
spring:
config:
import:
- nacos:service-order-test.yml
- nacos:database-test.yml?group=order
activate:
on-profile: test
```
## OpenFeign 使用
### 基础配置
#### 依赖引入
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>${spring-cloud.version}</version>
</dependency>
```
#### 启用Feign客户端
```java
@SpringBootApplication
@EnableFeignClients(basePackages = "com.yourpackage.feign")
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
```
> [!NOTE]
> `@EnableDiscoveryClient`注解Spring Cloud最新版本已自动启用服务发现功能。
### Feign客户端使用
#### 服务间调用
**Feign客户端定义**
```java
@FeignClient(
name = "service-product",
path = "/api/product",
configuration = ProductFeignConfig.class
)
public interface ProductFeignClient {
@GetMapping("/{id}")
ResponseEntity<Product> getProductById(@PathVariable("id") Long productId);
@PostMapping
ResponseEntity<Void> createProduct(@Valid @RequestBody Product product); // 添加@Valid注解支持参数校验
}
```
**服务调用示例优化**
```java
@Service
@RequiredArgsConstructor
public class OrderService {
private final ProductFeignClient productFeignClient;
public Order createOrder(Long productId, Long userId) {
Product product = productFeignClient.getProductById(productId)
.orElseThrow(() -> new ProductNotFoundException(productId)); // 使用Optional简化判断
return Order.builder()
.productId(product.getId())
.userId(userId)
// 其他订单属性
.build();
}
}
```
#### 第三方服务调用优化
```java
@FeignClient(
name = "bunny-client",
url = "${external.bunny.api.url}",
configuration = ExternalFeignConfig.class
)
public interface BunnyFeignClient {
@PostMapping("/login")
ResponseEntity<AuthResponse> login(@Valid @RequestBody LoginDto loginDto); // 使用具体返回类型替代String
}
```
### 负载均衡对比
| 特性 | 客户端负载均衡 (OpenFeign) | 服务端负载均衡 (Nginx等) |
| ------------ | ----------------------------------- | ------------------------ |
| **实现位置** | 客户端实现 | 服务端实现 |
| **依赖关系** | 需要服务注册中心 | 不依赖注册中心 |
| **性能** | 直接调用,减少网络跳转 | 需要经过代理服务器 |
| **灵活性** | 可定制负载均衡策略 | 配置相对固定 |
| **服务发现** | 集成服务发现机制 | 需要手动维护服务列表 |
| **适用场景** | 微服务内部调用 | 对外暴露API或跨系统调用 |
| **容错能力** | 集成熔断机制如Sentinel、Hystrix | 依赖代理服务器容错配置 |
### 高级配置
#### 日志配置优化
**application.yml**:
```yaml
logging:
level:
org.springframework.cloud.openfeign: DEBUG
com.yourpackage.feign: DEBUG
```
**Java配置**
```java
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
// 生产环境建议使用BASIC级别
return Logger.Level.FULL;
}
}
```
#### 超时与重试配置优化
**application.yml**:
```yaml
spring:
cloud:
openfeign:
client:
config:
default: # 全局默认配置
connect-timeout: 2000
read-timeout: 5000
logger-level: basic
retryable: false # 默认关闭重试,避免幂等问题
service-product: # 特定服务配置
connect-timeout: 3000
read-timeout: 10000
retryable: true
```
**重试机制配置**
```java
@Bean
public Retryer feignRetryer() {
// 重试间隔100ms最大间隔1s最大尝试次数3次
return new Retryer.Default(100, 1000, 3);
}
```
### Feign拦截器优化
#### 最佳实践
```java
@Component
@RequiredArgsConstructor
public class AuthRequestInterceptor implements RequestInterceptor {
private final AuthService authService;
@Override
public void apply(RequestTemplate template) {
template.header("Authorization", "Bearer " + authService.getCurrentToken());
}
}
```
**配置方式**
```yaml
spring:
cloud:
openfeign:
client:
config:
default:
request-interceptors:
- com.yourpackage.feign.interceptor.AuthRequestInterceptor
- com.yourpackage.feign.interceptor.LoggingInterceptor
```
### 熔断降级配置优化
#### 整合Sentinel
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>${sentinel.version}</version>
</dependency>
```
**配置**
```yaml
feign:
sentinel:
enabled: true
# 降级策略配置
fallback:
enabled: true
# 默认降级类路径
default: com.yourpackage.feign.fallback.DefaultFallback
```
**降级实现优化**
```java
@Slf4j
@Component
public class ProductFeignClientFallback implements ProductFeignClient {
@Override
public ResponseEntity<Product> getProductById(Long productId) {
log.warn("Product服务降级productId: {}", productId);
return ResponseEntity.ok(Product.getDefaultProduct(productId));
}
}
```
**FeignClient使用**
```java
@FeignClient(
name = "service-product",
path = "/api/product",
fallback = ProductFeignClientFallback.class,
fallbackFactory = ProductFeignClientFallbackFactory.class // 可选,用于获取异常信息
)
public interface ProductFeignClient {
// 方法定义
}
```