SpringClound 微服务分布式Nacos学习笔记

基本概述

在实际项目中,选择哪种架构需要根据具体的需求、团队能力和技术栈等因素综合考虑。

单体架构(Monolithic Architecture)

        单体架构是一种传统的软件架构风格,将整个应用程序构建为一个单一的、不可分割的单元。在这种架构中,所有的功能模块(如用户管理、订单处理、支付等)都打包在一个大型的、统一的代码库中,并且部署为一个单独的进程。

微服务架构(Microservices Architecture)

        微服务架构是一种将复杂应用程序分解为一组小型、独立服务的架构风格。每个微服务都围绕特定的业务功能构建,运行在其独立的进程中,并通过轻量级的通信机制(通常是HTTP/RESTful API、消息队列等)协同工作。例如,一个电商系统可以被拆分为用户服务(处理用户注册、登录等功能)、订单服务(负责订单创建、查询等)、库存服务(管理商品库存)等多个微服务。

总结

  • 单体架构适用于小型或简单的应用程序,开发团队规模较小,且对系统的扩展性和灵活性要求不高。

  • 微服务架构适用于复杂、大型的应用程序,特别是需要高可扩展性、灵活性和快速迭代的场景。开发团队需要具备分布式系统的开发和运维能力。

一、单体架构(Monolithic Architecture)

(一)定义

单体架构是一种传统的软件架构风格,将整个应用程序构建为一个单一的、不可分割的单元。在这种架构中,所有的功能模块(如用户管理、订单处理、支付等)都打包在一个大型的、统一的代码库中,并且部署为一个单独的进程。

(二)特点

  1. 集中式开发

    单体架构的代码通常集中在一个大型的代码库中,所有功能模块共享相同的代码库。开发团队需要在同一个代码库中协作,进行功能开发、修改和维护。
  2. 统一部署

    整个应用程序作为一个整体进行部署。每次更新或修复任何功能模块时,都需要重新打包并部署整个应用程序。例如,如果只是修复了一个小的用户界面问题,也需要重新部署整个系统。
  3. 紧密耦合

    功能模块之间通常存在紧密的依赖关系。一个模块的变更可能会影响到其他模块,因此在修改代码时需要非常谨慎,以避免引入新的问题。
  4. 可扩展性有限

    单体架构的扩展通常是通过增加服务器的性能(如CPU、内存)来实现的,这种扩展方式称为垂直扩展。当系统负载增加时,垂直扩展的代价会越来越高,且存在硬件资源的瓶颈。

(三)优点

  1. 简单易理解

    对于小型或简单的应用程序,单体架构的结构相对简单,开发和部署过程也较为直观。开发人员可以快速上手,不需要复杂的架构设计和分布式系统的知识。
  2. 开发工具友好

    大多数开发工具(如IDE)对单体架构的支持较好,调试和测试也相对容易。开发人员可以在一个统一的环境中进行开发,不需要考虑跨服务的调试问题。
  3. 事务管理简单

    在单体架构中,事务管理相对简单,因为所有的功能模块都在同一个进程中运行,可以通过传统的数据库事务来保证数据的一致性。

(四)缺点

  1. 可维护性差

    随着应用程序的规模增大,代码库会变得庞大且复杂,开发和维护成本会急剧上升。新功能的添加或现有功能的修改可能会引入新的问题,影响整个系统的稳定性。
  2. 部署困难

    由于每次更新都需要重新部署整个应用程序,部署过程可能会变得繁琐且耗时。同时,频繁的部署也可能对系统的稳定性产生影响。
  3. 技术栈受限

    单体架构通常使用单一的技术栈,很难引入新的技术或框架。一旦选择了某种技术,后续很难进行技术的替换或升级。

二、微服务架构(Microservices Architecture)

(一)定义

微服务架构是一种将复杂应用程序分解为一组小型、独立服务的架构风格。每个微服务都围绕特定的业务功能构建,运行在其独立的进程中,并通过轻量级的通信机制(通常是HTTP/RESTful API、消息队列等)协同工作。

(二)特点

  • 独立性

        独立开发:每个微服务可以由不同的团队独立开发,团队之间只需要通过定义好的接口进行协作。例如,用户服务团队和订单服务团队可以分别开发和维护自己的服务,而不需要相互干扰。

        独立部署:每个微服务可以独立部署,更新或扩展一个微服务不会影响到其他微服务。例如,当订单服务需要更新订单处理逻辑时,只需要重新部署订单服务相关的代码和配置。

        独立扩展:可以根据每个微服务的负载情况独立扩展。如果用户服务的访问量突然增加,可以单独增加用户服务的实例数量,而不需要对其他服务进行扩展。

  • 容错性

    单个微服务的故障不会导致整个系统崩溃。例如,如果库存服务暂时不可用,订单服务仍然可以正常接收订单,只是可能无法实时更新库存信息,但系统可以设计为在这种情况下记录订单并等待库存服务恢复后再处理库存更新。
  • 可维护性高

    由于每个微服务都相对独立,代码库较小,开发和维护成本相对较低。新功能的添加或现有功能的修改不会对整个系统产生太大的影响。

(三)优点

  1. 敏捷开发

    微服务架构支持敏捷开发,不同的团队可以并行开发不同的微服务,加快开发速度。同时,独立部署的特点也使得新功能可以快速上线。
  2. 可扩展性强

    微服务架构支持水平扩展,可以根据每个微服务的负载情况独立扩展。例如,通过增加微服务实例的数量来应对高并发场景。
  3. 技术灵活性

    开发团队可以自由选择最适合的技术栈来开发每个微服务,便于引入新技术和框架。
  4. 容错性好

    单个微服务的故障不会导致整个系统崩溃,系统的整体可用性更高。

(四)缺点

  1. 分布式系统复杂性

    微服务架构本质上是一个分布式系统,需要处理分布式事务、服务间通信、数据一致性等问题。例如,一个业务流程可能涉及多个微服务的调用,需要解决分布式事务问题。
  2. 部署和运维复杂

    微服务架构需要管理多个独立的服务,部署和运维的复杂度会增加。例如,需要管理每个微服务的配置、监控、日志等。
  3. 性能开销

    由于微服务之间通过网络通信(如HTTP/RESTful API、消息队列),可能会引入额外的性能开销。例如,服务间的调用延迟可能会比单体架构中的方法调用延迟更高。
  4. 数据一致性挑战

    微服务架构中,数据通常分散在不同的微服务中,需要解决数据一致性问题。例如,订单服务和库存服务可能分别存储订单信息和库存信息,需要通过分布式事务或事件驱动的方式来保证数据的一致性。

三、单体架构与微服务架构的对比

特点单体架构微服务架构
开发方式集中式开发,所有功能模块在同一个代码库中分布式开发,每个微服务独立开发
部署方式统一部署,更新需要重新部署整个应用程序独立部署,更新或扩展一个微服务不影响其他微服务
技术栈通常使用单一技术栈可以使用多种技术栈
可维护性小型应用简单,大型应用复杂且难以维护可维护性高,每个微服务相对独立
扩展性垂直扩展,受限于硬件资源水平扩展,可以根据负载独立扩展每个微服务
容错性一个模块的故障可能导致整个系统崩溃单个微服务的故障不会影响整个系统
事务管理传统数据库事务管理简单需要分布式事务管理
性能开销方法调用性能高,没有额外的网络通信开销服务间通信可能引入额外的性能开销
开发工具开发工具友好,调试和测试简单开发工具支持有限,调试和测试复杂
适用场景小型或简单的应用程序复杂、大型的应用程序,需要高可扩展性和灵活性

SpringClound

 官网地址:  Spring Cloud

SpringCloud是目前国内使用最广泛的微服务框架。集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。

微服务拆分

原则:

微服务通信

当我们将微服务拆分后,各自的资源会存在独立管理的状态,当某个服务需要调用另一个服务的资源的时候。我们可以通过 网络通信 的方式,进行各自的资源交互。

Spring RestTemplate工具

Spring给我们提供了一个RestTemplate工具,可以方便的实现Http请求的发送。使用步骤如下:

1、注入RestTemplate到Spring容器
    @Bean
    public 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的map
        Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
        // 4.写入vo
        for (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
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:8848
        username: nacos
        password: nacos

3、启动服务

可以看到服务注册成功。

服务发现

@Autowired
private RestTemplate restTemplate;
@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:8081
        URI 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();
}

通过测试可以发现,实现了负载均衡。

OpenFeign

Openfeign是一个声明式的http客户端,是SpringCloud在Eureka公司开源的Feign基础上改造而来。官方地址: https://github.com/openFeign/feign
其作用就是基于SpringMVC的常见注解,帮我们优雅的实现http请求的发送。

快速入门

微服务通信刨析

引入依赖
  <!--openFeign-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
  </dependency>
  <!--负载均衡器-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  </dependency>
通过@EnableFeignClients注解,启用OpenFeign功能
@EnableFeignClients        // 开启 openFeign 请求工具
@MapperScan("com.angindem.mapper")
@SpringBootApplication
public class CartApplication {
    public static void main(String[] args) {
        SpringApplication.run(CartApplication.class, args);
    }
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
编写FeignClient
package com.angindem.clien;
import com.angindem.domain.dto.ItemDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Collection;
import java.util.List;
@FeignClient("item-service")
public interface ItemClient {
    @GetMapping("/items")
    List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}
调用
@Autowired
private ItemClient itemClient;
private void DemoItems(){
        List<ItemDTO> items = response.getBody();
}

连接池

OpenFeign对Http请求做了优雅的伪装,不过其底层发起http请求,依赖于其它的框架。这些框架可以自己选择,包括以下三种:

  • HttpURLconnection:默认实现,不支持连接池
  • Apache HttpClient:支持连接池
  • OKHttp:支持连接池

具体源码可以参考FeignBlockingLoadBalancerClient类中的delegate成员变量。

引入依赖

这里使用 OKhttp

<!--OK http 的依赖 -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>
启动连接池

yaml配置

feign:
  okhttp:
    enabled: true

最佳实践

方案一:

优点:

  • 模块化:每个服务模块独立开发、部署和扩展,便于维护和升级。

  • 解耦:服务之间通过定义好的接口进行通信,降低了耦合度。

  • 可扩展性:可以根据需要独立扩展各个服务模块,提高系统的可扩展性。

  • 灵活性:可以根据业务需求灵活组合不同的服务模块,实现复杂的业务逻辑。

缺点:

  • 复杂性

    • 管理多个服务增加了系统的复杂性。

    • 需要额外的工具和服务来管理服务间的通信和数据一致性。

这种微服务架构适用于大型分布式系统,可以提高系统的可维护性、可扩展性和灵活性。

方案二:

优点:

  •     集中化API管理

        hm-api作为API网关,统一管理对外的API接口,简化了客户端与后端服务的交互。

  •     解耦:

        服务之间通过API网关进行通信,降低了服务之间的直接依赖,提高了系统的灵活性和可维护性。

缺点:

  •     复杂性增加:

        引入API网关增加了系统的复杂性,需要额外的配置和管理。

  •     性能开销:

        API网关可能会引入额外的网络延迟,影响系统性能。

  •     单点故障:

        如果API网关设计不当,可能会成为系统的单点故障,影响整体可用性。

        总的来说,引入API网关可以带来许多好处,如集中化管理、解耦,但也需要注意其带来的复杂性和性能开销。在设计和实施API网关时,需要权衡这些因素,确保系统的稳定性和可维护性。

扫描包问题

        当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种方式解决。

方式一:指定FeignClient所在包

@EnableFeignClients(basePackages = "com.angindem.api.clients")

方式二:指定Feignclient字节码

@EnableFeignClients(clients ={ItemClient.class})

日志输出

Openfeiqn只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:

  • ONONE:不记录任何日志信息,这是默认值。
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASI℃的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据,

由于Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。

要自定义日志级别需要声明一个类型为Logger.Level的Bean,在其中定义日志级别。

package com.angindem.api.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
public class DefaultFeignConfig {
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}
局部声明
@FeignClient(value = "item-service",configuration = DefaultFeignConfig.class)
全局声明
@EnableFeignClients(clients ={ItemClient.class},defaultConfiguration = DefaultFeignConfig.class)

Spring gateway 网关

        在Java微服务架构中,网关路由是实现服务间通信和请求管理的关键组件。它作为微服务架构的入口,接收外部请求并根据请求的特征(如URL、HTTP方法、参数等)将请求转发到对应的服务实例。网关路由不仅负责请求的转发,还承担了诸如身份验证、授权、限流、熔断、日志记录等多种职责,从而为微服务提供统一的接入点和安全控制,简化了客户端与服务之间的交互复杂性,提高了系统的可维护性和可扩展性。简要概述:就是网络的关口,负责请求的路由、转发、身份校验。

快速入门

引入依赖
        <!--网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--nacos discovery-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--负载均衡-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
yaml配置
server:
  port: 8080

spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: localhost:8848
      username: nacos
      password: nacos
    gateway:
      routes:
        - id: item # 路由规则id,自定义,唯一
          uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
          predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
            - Path=/items/**,/search/** # 这里是以请求路径作为判断规则

路由属性

网关路由对应的Java类型是RouteDefinition,其中常见的属性有:

  • id:路由唯一标识
  • uri:路由目标地址
  • predicates:路由断言,判断请求是否符合当前路由
  • filters:路由过滤器,对请求或响应做特殊处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值