day_0 分布式入门: 基本知识+Nacos基础
前言
现在大二下都已经结束了,之前Java学完基础的Spring Boot也就没再往下学,硬是把自己的智能体项目往下推进了两个月。现在暑假还挺长,是时候补补微服务相关的东西。
虽说网上都说今年是“智能体元年”,各种智能体应用也都层出不穷,但它们很多都还是集中在“加个prompt、微调一下、接入RAG”这些操作上。这两年虽然也有Manus、Open Hands、MiniMax等这类ReAct智能体,但好像也都还处在起步阶段,token费用、模型理解力、长期记忆的稳定性、Prompt遵循程度也确实还需要进一步发展才能支撑其大规模商业化吧🤔。
不过从这些智能体应用的发展趋势来看,这两年“大模型应用工程”说不准还真能成为一个新的行业风口。
不管怎么说吧,后端+Agent相关技术栈,或许还真是一条路,而且这条路自己也倒感兴趣。
分布式基础
这次是跟着尚硅谷的七小时快速通关课程学的,接下来的学习目前来说并不打算跟着教学课程做他们的完整教学项目,而是想着补充好Spring Cloud知识后,去读一些开源项目源码,了解它们的思路、架构、技术,然后围绕自己的智能体项目做一些扩展与适配,毕竟可操作空间挺多的。这条路也没走过,但应该可行。
常见方案
架构演进
单体
-
优点: 开发部署简便
-
缺点: 无法应对高并发
集群
- 优点: 解决大并发
- 缺点: 模块化升级不便、多语言协作不便
分布式
将一个大型应用拆分成很多小应用分布部署在各个机器。
- RPC 远程调用
- 服务熔断
- 服务雪崩
- 负载均衡
- 单点故障
- 分布式事务
基础项目创建
项目工程结构
依赖配置
<properties>
<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>
<java.version>17</java.version>
</properties>
Nacos
安装
平常用的系统也都是Manjaro了,安装也就不跟着教程来,上docker吧,方便一些。
#!/bin/bash
docker run --name nacos-standalone-derby \
-e MODE=standalone \
-e NACOS_AUTH_TOKEN=RhU59ICF0TbzX1O4ppm3h338IccghQrdiRCdmfafGghfvb8W \
-e NACOS_AUTH_IDENTITY_KEY=slhaf_nacos \
-e NACOS_AUTH_IDENTITY_VALUE=nacos \
-p 8080:8080 \
-p 8848:8848 \
-p 9848:9848 \
-d nacos/nacos-server:latest
在项目中引入Nacos发现依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
在需要被发现的服务(Spring Boot项目)中配置Nacos地址和服务名称:
spring.cloud.nacos.server-addr=127.0.0.1:8848
spring.application.name=service-product
功能
注册中心
服务注册
引入Nacos依赖,在项目中做好Nacos服务配置后,启动项目,可以在Nacos中看到新启动的服务
服务发现与远程调用
注解: @EnableDiscoveryClient
开启服务发现,但现在不加这个注解也能正常发现服务。
开启服务发现后,通过类DiscoveryClient
或NacosServiceDiscovery
可以发现远程服务,进而获取它们的IP、端口进行不同服务间的通信。
示例:
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private NacosServiceDiscovery nacosServiceDiscovery;
@Test
void discoveryClientTest() {
for (String service : discoveryClient.getServices()) {
System.out.println("service: " + service);
discoveryClient.getInstances(service).forEach(instance -> {
System.out.println("IP & PORT -> "+ instance.getHost() + ":" + instance.getPort());
});
}
}
@Test
void nacosServiceDiscoveryTest() throws Exception {
for (String service : nacosServiceDiscovery.getServices()) {
System.out.println("service: "+service);
nacosServiceDiscovery.getInstances(service).forEach(instance -> {
System.out.println("IP & PORT -> "+ instance.getHost() + ":" + instance.getPort());
});
}
}
负载均衡
依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
在Spring中提供了一个RestTemplate
可以用于发起 网络请求、接受响应并转化为对应的实体类。使用如下:
@Test
void test() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getForObject("http://127.0.0.1:9000/product/1", String.class);
}
实现1
loadbalancer
依赖也为其提供了@LoadBalanced注解,配合
RestTemplate`的Bean Configuration可以更方便地实现负载均衡。
开启负载均衡以及允许通过依赖注入获取实例:
@Configuration
public class ProductConfiguration {
@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
通过RestTemplate
发起请求,此时请求的URL中ip地址可以直接填为服务名称:
@Autowired
RestTemplate restTemplate;
@Test
void test() {
String res = restTemplate.getForObject("http://service-product/product", String.class);
System.out.println(res);
}
结果:
实现2
除了借助@LoadBalanced
注解外,也可以通过LoadBalancerClient
类选出实例,然后发送请求:
@Autowired
private LoadBalancerClient loadBalancerClient;
@Test
void test2() {
RestTemplate restTemplate = new RestTemplate();
ServiceInstance choose = loadBalancerClient.choose("service-product");
String forObject = restTemplate.getForObject(getUrl(choose), String.class);
System.out.println(forObject);
}
private String getUrl(ServiceInstance choose) {
return "http://"+choose.getHost()+":"+choose.getPort()+"/product";
}
但此时使用的restTemplate
不能同时开启@LoadBalanced
注册,否则会提示No instances available
单例安全原因
看视频时,老师提到RestTemplate
是‘线程安全的’,于是我就想着应该是这个类发送请求的流程中没有用到共享的变量,然后顺着走下去可以看到在整条调用链以及整条调用链末尾的doExecute
方法中都没有涉及到共享的变量。
配置中心
依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
引入依赖后,必须配置spring.config.import
相关内容,或者关闭import检查,否则会出现如下报错:
基本用法
通过在配置文件中进行类似相关配置,可以使得项目在启动时从远程Nacos配置中心获取相应的配置文件
spring:
config:
import:
- nacos:common.properties
- nacos:database.properties
此时在远程修改Nacos配置,项目可以收到对应的事件推送,并对配置进行动态更新
配置可以通过@Value
和@RefreshScope
注解进行刷新,前者可以获取配置文件中的属性,后者为注解所在的Bean
开启动态刷新功能。
@RestController
@Slf4j
@RefreshScope
public class ProductController {
@Value("${timeout}")
private String timeout;
@GetMapping("/product")
public String getProduct(){
return timeout;
}
}
也可以通过@ConfigurationProperties
来动态注入,此时不再需要@RefreshScope
注解
@Data
@Component
@ConfigurationProperties(prefix = "order")
public class OrderProperties {
private String db;
private String timeout;
private String autoConfirm;
}
@RestController
public class OrderController {
@Autowired
private OrderProperties orderProperties;
@GetMapping("/order")
public String getOrder(){
return orderProperties.toString();
}
}
配置监听
通过NacosConfigManager
可以监听配置变化
@EnableConfigurationProperties
@SpringBootApplication
@Slf4j
public class ProductMainApplication {
public static void main(String[] args) {
SpringApplication.run(ProductMainApplication.class, args);
}
@Bean
ApplicationRunner applicationRunner(NacosConfigManager nacosConfigManager) {
return args -> {
nacosConfigManager.getConfigService().addListener("service-product.properties", "DEFAULT_GROUP", new Listener() {
@Override
public Executor getExecutor() {
return Executors.newFixedThreadPool(4);
}
@Override
public void receiveConfigInfo(String configInfo) {
log.info("receiveConfigInfo: {}", configInfo);
}
});
};
}
}
nacosConfigManager.getConfigService().addListener
可以指定dataId
、group
,以及一个监听器。
在监听器中可以指定监听所用的线程池和收到推送事件后触发的操作,不过这里的线程池应该使用虚拟线程池更合适些吧。
配置切换
在Nacos中可以为不同的服务以及服务的不同环境配置namespace和group,并在配置文件中进行指定,达到配置文件的动态切换
示例如下:
server:
port: 9100
spring:
profiles:
active: test
application:
name: server-order
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
namespace: ${spring.profiles.active}
group: order
---
spring:
config:
import:
- nacos:common.properties
- nacos:database.properties
activate:
on-profile: dev
---
spring:
config:
import:
- nacos:common.properties
- nacos:database.properties
activate:
on-profile: test