SpringClound 微服务分布式Nacos学习笔记
一、基本概述
在实际项目中,选择哪种架构需要根据具体的需求、团队能力和技术栈等因素综合考虑。
单体架构(Monolithic Architecture)
单体架构是一种传统的软件架构风格,将整个应用程序构建为一个单一的、不可分割的单元。在这种架构中,所有的功能模块(如用户管理、订单处理、支付等)都打包在一个大型的、统一的代码库中,并且部署为一个单独的进程。
微服务架构(Microservices Architecture)
微服务架构是一种将复杂应用程序分解为一组小型、独立服务的架构风格。每个微服务都围绕特定的业务功能构建,运行在其独立的进程中,并通过轻量级的通信机制(通常是HTTP/RESTful API、消息队列等)协同工作。例如,一个电商系统可以被拆分为用户服务(处理用户注册、登录等功能)、订单服务(负责订单创建、查询等)、库存服务(管理商品库存)等多个微服务。
总结
-
单体架构适用于小型或简单的应用程序,开发团队规模较小,且对系统的扩展性和灵活性要求不高。
-
微服务架构适用于复杂、大型的应用程序,特别是需要高可扩展性、灵活性和快速迭代的场景。开发团队需要具备分布式系统的开发和运维能力。
一、单体架构(Monolithic Architecture)
(一)定义
单体架构是一种传统的软件架构风格,将整个应用程序构建为一个单一的、不可分割的单元。在这种架构中,所有的功能模块(如用户管理、订单处理、支付等)都打包在一个大型的、统一的代码库中,并且部署为一个单独的进程。
(二)特点
-
集中式开发
单体架构的代码通常集中在一个大型的代码库中,所有功能模块共享相同的代码库。开发团队需要在同一个代码库中协作,进行功能开发、修改和维护。 -
统一部署
整个应用程序作为一个整体进行部署。每次更新或修复任何功能模块时,都需要重新打包并部署整个应用程序。例如,如果只是修复了一个小的用户界面问题,也需要重新部署整个系统。 -
紧密耦合
功能模块之间通常存在紧密的依赖关系。一个模块的变更可能会影响到其他模块,因此在修改代码时需要非常谨慎,以避免引入新的问题。 -
可扩展性有限
单体架构的扩展通常是通过增加服务器的性能(如CPU、内存)来实现的,这种扩展方式称为垂直扩展。当系统负载增加时,垂直扩展的代价会越来越高,且存在硬件资源的瓶颈。
(三)优点
-
简单易理解
对于小型或简单的应用程序,单体架构的结构相对简单,开发和部署过程也较为直观。开发人员可以快速上手,不需要复杂的架构设计和分布式系统的知识。 -
开发工具友好
大多数开发工具(如IDE)对单体架构的支持较好,调试和测试也相对容易。开发人员可以在一个统一的环境中进行开发,不需要考虑跨服务的调试问题。 -
事务管理简单
在单体架构中,事务管理相对简单,因为所有的功能模块都在同一个进程中运行,可以通过传统的数据库事务来保证数据的一致性。
(四)缺点
-
可维护性差
随着应用程序的规模增大,代码库会变得庞大且复杂,开发和维护成本会急剧上升。新功能的添加或现有功能的修改可能会引入新的问题,影响整个系统的稳定性。 -
部署困难
由于每次更新都需要重新部署整个应用程序,部署过程可能会变得繁琐且耗时。同时,频繁的部署也可能对系统的稳定性产生影响。 -
技术栈受限
单体架构通常使用单一的技术栈,很难引入新的技术或框架。一旦选择了某种技术,后续很难进行技术的替换或升级。
二、微服务架构(Microservices Architecture)
(一)定义
微服务架构是一种将复杂应用程序分解为一组小型、独立服务的架构风格。每个微服务都围绕特定的业务功能构建,运行在其独立的进程中,并通过轻量级的通信机制(通常是HTTP/RESTful API、消息队列等)协同工作。
(二)特点
-
独立性
独立开发:每个微服务可以由不同的团队独立开发,团队之间只需要通过定义好的接口进行协作。例如,用户服务团队和订单服务团队可以分别开发和维护自己的服务,而不需要相互干扰。
独立部署:每个微服务可以独立部署,更新或扩展一个微服务不会影响到其他微服务。例如,当订单服务需要更新订单处理逻辑时,只需要重新部署订单服务相关的代码和配置。
独立扩展:可以根据每个微服务的负载情况独立扩展。如果用户服务的访问量突然增加,可以单独增加用户服务的实例数量,而不需要对其他服务进行扩展。
-
容错性
单个微服务的故障不会导致整个系统崩溃。例如,如果库存服务暂时不可用,订单服务仍然可以正常接收订单,只是可能无法实时更新库存信息,但系统可以设计为在这种情况下记录订单并等待库存服务恢复后再处理库存更新。 -
可维护性高
由于每个微服务都相对独立,代码库较小,开发和维护成本相对较低。新功能的添加或现有功能的修改不会对整个系统产生太大的影响。
(三)优点
-
敏捷开发
微服务架构支持敏捷开发,不同的团队可以并行开发不同的微服务,加快开发速度。同时,独立部署的特点也使得新功能可以快速上线。 -
可扩展性强
微服务架构支持水平扩展,可以根据每个微服务的负载情况独立扩展。例如,通过增加微服务实例的数量来应对高并发场景。 -
技术灵活性
开发团队可以自由选择最适合的技术栈来开发每个微服务,便于引入新技术和框架。 -
容错性好
单个微服务的故障不会导致整个系统崩溃,系统的整体可用性更高。
(四)缺点
-
分布式系统复杂性
微服务架构本质上是一个分布式系统,需要处理分布式事务、服务间通信、数据一致性等问题。例如,一个业务流程可能涉及多个微服务的调用,需要解决分布式事务问题。 -
部署和运维复杂
微服务架构需要管理多个独立的服务,部署和运维的复杂度会增加。例如,需要管理每个微服务的配置、监控、日志等。 -
性能开销
由于微服务之间通过网络通信(如HTTP/RESTful API、消息队列),可能会引入额外的性能开销。例如,服务间的调用延迟可能会比单体架构中的方法调用延迟更高。 -
数据一致性挑战
微服务架构中,数据通常分散在不同的微服务中,需要解决数据一致性问题。例如,订单服务和库存服务可能分别存储订单信息和库存信息,需要通过分布式事务或事件驱动的方式来保证数据的一致性。
三、单体架构与微服务架构的对比
特点 | 单体架构 | 微服务架构 |
开发方式 | 集中式开发,所有功能模块在同一个代码库中 | 分布式开发,每个微服务独立开发 |
部署方式 | 统一部署,更新需要重新部署整个应用程序 | 独立部署,更新或扩展一个微服务不影响其他微服务 |
技术栈 | 通常使用单一技术栈 | 可以使用多种技术栈 |
可维护性 | 小型应用简单,大型应用复杂且难以维护 | 可维护性高,每个微服务相对独立 |
扩展性 | 垂直扩展,受限于硬件资源 | 水平扩展,可以根据负载独立扩展每个微服务 |
容错性 | 一个模块的故障可能导致整个系统崩溃 | 单个微服务的故障不会影响整个系统 |
事务管理 | 传统数据库事务管理简单 | 需要分布式事务管理 |
性能开销 | 方法调用性能高,没有额外的网络通信开销 | 服务间通信可能引入额外的性能开销 |
开发工具 | 开发工具友好,调试和测试简单 | 开发工具支持有限,调试和测试复杂 |
适用场景 | 小型或简单的应用程序 | 复杂、大型的应用程序,需要高可扩展性和灵活性 |
SpringClound
官网地址: Spring Cloud
SpringCloud是目前国内使用最广泛的微服务框架。集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。
微服务拆分
原则:
微服务通信
当我们将微服务拆分后,各自的资源会存在独立管理的状态,当某个服务需要调用另一个服务的资源的时候。我们可以通过 网络通信 的方式,进行各自的资源交互。
Spring RestTemplate工具
Spring给我们提供了一个RestTemplate工具,可以方便的实现Http请求的发送。使用步骤如下:
1、注入RestTemplate到Spring容器
@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}
2、简单的Get引用获取交换资源
@RequiredArgsConstructor
public class demoServiceImpl extends ServiceImpl<demoMapper, demo> implements IdemoService {private final RestTemplate restTemplate;private void demo(){// 2.1 . 利用 RestTemplate 发起 http 请求,得到 http 的响应ResponseEntity<List<ItemDTO>> response = restTemplate.exchange("http://localhost:8081/items?ids={ids}",HttpMethod.GET,null,new ParameterizedTypeReference<List<ItemDTO>>() {},Map.of("ids", CollUtil.join(itemIds, ",")));// 2.2 . 解析响应if(!response.getStatusCode().is2xxSuccessful()){
// 响应失败,直接结束return ;}List<ItemDTO> items = response.getBody();if (CollUtils.isEmpty(items)) {return;}// 3.转为 id 到 item的mapMap<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));// 4.写入vofor (CartVO v : vos) {ItemDTO item = itemMap.get(v.getItemId());if (item == null) {continue;}v.setNewPrice(item.getPrice());v.setStatus(item.getStatus());v.setStock(item.getStock());}}
}
服务治理-nacos
微服务远程通信问题
当我们多个服务运行后,其中某个服务挂机,而我们写的请求URL写死了,导致获取不到资源。又亦或重启服务后,端口方面有变动,我们又得要从源码中更改,这是不理想的情况的。
注册中心原理
什么是注册中心
在分布式系统中,服务会有很多实例,这些实例分布在不同的机器上。注册中心就像是一个“服务中心”,它记录了所有服务实例的信息,包括服务名称、实例的地址(IP和端口)等。当一个服务(客户端)需要调用另一个服务(服务提供者)时,它会先去注册中心查询服务提供者的地址,然后才能进行通信。
Nacos 注册中心
Nacos是目前国内企业中占比最多的注册中心组件。它是阿里巴巴的产品,目前已经加入SpringCloud Alibaba中。(官网:Nacos官网| Nacos 配置中心 | Nacos 下载| Nacos 官方社区 | Nacos 官网)
Nacos搭建步骤
1、配置MySQL表
我们基于Docker来部署Nacos的注册中心,首先我们要准备MySQL数据库表,用来存储Nacos的数据。官方Nacos-SQL表(mysql-schema.sql)
这里注意执行脚本前,自定义一个自己的数据库,同时添加用户信息(密码:nacos)
CREATE DATABASE nacos;
USE nacos;
-- 最后添加用户信息
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
2、dockers部署
- 拉取镜像
docker pull nacos/nacos-server:v2.5.1-slim
- 通过 SSL 执行生成随机base64的 NACOS_AUTH_TOKEN
openssl rand -base64 32
- 一键命令创建并启动镜像(系统参数 | Nacos 官网)
docker run -d \
--name nacos \
-e MODE=standalone \
-e PREFER_HOST_MODE=hostname \
-e NACOS_AUTH_ENABLE=true \
-e SPRING_DATASOURCE_PLATFORM=mysql \
-e MYSQL_SERVICE_HOST=localhost \
-e MYSQL_SERVICE_PORT=3306 \
-e MYSQL_SERVICE_USER=root \
-e MYSQL_SERVICE_DB_NAME=nacos \
-e MYSQL_SERVICE_PASSWORD=123456 \
-e MYSQL_SERVICE_DB_PARAM='characterEncoding=utf8&connectTimeout=10000&socketTimeout=30000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai' \
-e NACOS_AUTH_ENABLE=true \
-e NACOS_AUTH_TOKEN='eZCEZeAxiQvbsTJMtc518ocS4vtiEwBTDqGVvk3FPww=' \
-e NACOS_AUTH_TOKEN_EXPIRE_SECONDS=18000 \
-e NACOS_AUTH_IDENTITY_KEY=nacos \
-e NACOS_AUTH_IDENTITY_VALUE=123456 \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
nacos/nacos-server:v2.5.1-slim
启动成功后,通过 logs 可以看到
访问 http://localhost:8848/nacos/,就进入到需要登录的页面。
根据我们配置的MySQL默认账号密码都为:Nacos。登录即可看到页面
服务注册
1、引入依赖
父工程:
<!--spring cloud-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope>
</dependency>
<!--spring cloud alibaba-->
<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>
子工程:
<!--nacos 服务注册发现-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2、配置 yaml 文件
spring:cloud:nacos:discovery:server-addr: localhost:8848username: nacospassword: nacos
3、启动服务
可以看到服务注册成功。
服务发现
@Autowired
private DiscoveryClient discoveryClient;private void DemoItems(){// 根据服务名称获取服务的实例列表List<ServiceInstance> instances = discoveryClient.getInstances("item-service");if(CollUtils.isEmpty(instances)){
// 没有可用的服务,直接结束return ;}// 使用 cn.hutool.core.util.RandomUtil; 工具包随机获取一个实例ServiceInstance serviceInstance = instances.get(RandomUtil.randomInt(instances.size()));
// 获取服务的 URI : http://localhost:8081URI uri = serviceInstance.getUri();// 2.1 . 利用 RestTemplate 发起 http 请求,得到 http 的响应ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(uri + "/items?ids={ids}",HttpMethod.GET,null,new ParameterizedTypeReference<List<ItemDTO>>() {},Map.of("ids", CollUtil.join(itemIds, ",")));// 2.2 . 解析响应if(!response.getStatusCode().is2xxSuccessful()){
// 响应失败,直接结束return ;}List<ItemDTO> items = response.getBody();
}
通过测试可以发现,实现了负载均衡。