单体架构、微服务组件与解决方案

资料:
微服务、MQ资料
链接: https://pan.baidu.com/s/1nzCJ-hNw854uFJQf6jWobg 提取码: yyds

单体架构

在这里插入图片描述

微服务

拆分与改进

在这里插入图片描述

将单体项目 拆分成微服务项目

在这里插入图片描述

在这里插入图片描述

1、拆分原则

在这里插入图片描述

2、 拆分服务

在这里插入图片描述

3、远程调用

RestTemplate

在这里插入图片描述


        // 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;
        }

        // 处理items数据...

4、服务 远程调用存在的 问题

在这里插入图片描述

pom文件


<?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>

    <groupId>com.heima</groupId>
    <artifactId>hmall</artifactId>
    <packaging>pom</packaging>
    <version>1.0.0</version>
    <modules>
        <module>hm-common</module>
        <module>hm-service</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.12</version>
        <relativePath/>
    </parent>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <org.projectlombok.version>1.18.20</org.projectlombok.version>
        <spring-cloud.version>2021.0.3</spring-cloud.version>
        <spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
        <mybatis-plus.version>3.4.3</mybatis-plus.version>
        <hutool.version>5.8.11</hutool.version>
        <mysql.version>8.0.23</mysql.version>
    </properties>

    <!-- 对依赖包进行管理  只是版本控制-->
    <dependencyManagement>
        <dependencies>
            <!--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>
            <!-- 数据库驱动包管理 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <!-- mybatis plus 管理 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <!--hutool工具包-->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!-- lombok 管理 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${org.projectlombok.version}</version>
        </dependency>
        <!--单元测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <source>11</source> <!-- depending on your project -->
                        <target>11</target> <!-- depending on your project -->
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
	
	
</project>


注册中心

原理

在这里插入图片描述

在这里插入图片描述

服务注册、发现、负载均衡

1、服务注册


<!-- nacos 服务注册发现 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>


spring:
  application:
    name: item-service # 服务名称
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848 # nacos地址

2、服务发现

在这里插入图片描述


    @Autowired
    private DiscoveryClient discoveryClient;
    @Autowired
    private  RestTemplate restTemplate;

    /**
     * 根据商品ID列表查询商品信息
     */
    public List<ItemDTO> fetchItemsByIds(List<Long> itemIds) {

        // 2.1.根据服务名称获取服务的实例列表
        List<ServiceInstance> instances = discoveryClient.getInstances ("item-service");
        if (CollUtil.isEmpty (instances)) {
            return null; // 修改: 返回null或抛出异常,根据业务需求调整
        }

        // 2.2.手写负载均衡,从实例列表中挑选一个实例
        ServiceInstance instance = instances.get (RandomUtil.randomInt(instances.size ()));

        // 2.3.利用RestTemplate发起http请求,得到http的响应
        ResponseEntity<List<ItemDTO>> response = restTemplate.exchange (
                instance.getUri () + "/items?ids={ids}",
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<ItemDTO>> () {
                },
                Map.of ("ids", CollUtil.join (itemIds, ",")));


        return response.getBody ();
    }

OpenFeign 远程调用

声明式 HTTP客户端------>>> 发送HTTP请求

  • 引入依赖

<!-- nacos 服务注册发现 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- 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>

<!--httpClient的依赖 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

  • 启动类加注解 @EnableFeignClients,启用OpenFeign功能

@EnableFeignClients
@SpringBootApplication
public class CartApplication { 
    // ... 略 
}

  • yaml文件

# 配置nacos地址,找到提供者地址

spring:
  application:
    name: item-consumer # 服务名称
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848 # nacos地址

feign:
  client:
    config:
      default: # default全局的配置
        loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
  httpclient:
    enabled: true # 开启 feign对HttpClient的支持
    max-connections: 200 # 最大的连接数
    max-connections-per-route: 50 # 每个路径的最大连接数



  • 在消费者 编写代码

在这里插入图片描述

在这里插入图片描述

最佳实践

在这里插入图片描述

** 耦合度较高 将所有 feign-api 放在一起维护**

在这里插入图片描述

修改注解

@EnableFeignClients(basePackages = “com.example.feignapi”)—>不然 找不到会报错

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;


@SpringBootApplication
@EnableFeignClients(basePackages = "com.example.feignapi")
public class EurekaClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaClientApplication.class, args);
    }


}

解决 Feign 远程调用包扫描 问题

报错:

在这里插入图片描述
解决

方式一:

指定Feign应该扫描的包:

@EnableFeignClients(basePackages = "com.example.feignapi")

方式二:

指定需要加载的Client接口:

@EnableFeignClients(clients = {UserClient.class})

网关

1、 快速入门

在这里插入图片描述

快速入门

在这里插入图片描述


<dependencies><!--common--><dependency><groupId>com.heima</groupId><artifactId>hm-common</artifactId><version>1.0.0</version></dependency><!--网关--><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></dependencies><build><finalName>${project.artifactId}</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>



@SpringBootApplicationpublic class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}}

路由配置


server:
  port: 8080

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

2、路由属性

在这里插入图片描述

在这里插入图片描述

3、网关请求 处理流程

网关请求 处理流程

在这里插入图片描述

网关登录校验

在这里插入图片描述

1、 用户信息 保存到请求头

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

hm:
  jwt:
    location: classpath:hmall.jks # 秘钥地址
    alias: hmall # 秘钥别名
    password: hmall123 # 秘钥文件密码
    tokenTTL: 30m # 登录有效期
  auth:
    excludePaths: # 无需登录校验的路径
      - /search/**
      - /users/login
      - /items/**

package com.hmall.gateway.filter;

import com.hmall.common.exception.UnauthorizedException;
import com.hmall.common.utils.CollUtils;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final JwtTool jwtTool;

    private final AuthProperties authProperties;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取Request
        ServerHttpRequest request = exchange.getRequest();
        // 2.判断是否不需要拦截
        if(isExclude(request.getPath().toString())){
            // 无需拦截,直接放行
            return chain.filter(exchange);
        }
        // 3.获取请求头中的token
        String token = null;
        List<String> headers = request.getHeaders().get("authorization");
        if (!CollUtils.isEmpty(headers)) {
            token = headers.get(0);
        }
        // 4.校验并解析token
        Long userId = null;
        try {
            userId = jwtTool.parseToken(token);
        } catch (UnauthorizedException e) {
            // 如果无效,拦截
            ServerHttpResponse response = exchange.getResponse();
            response.setRawStatusCode(401);
            return response.setComplete();
        }

        // TODO 5.如果有效,传递用户信息
        System.out.println("userId = " + userId);
        // 6.放行
        return chain.filter(exchange);
    }

    private boolean isExclude(String antPath) {
        for (String pathPattern : authProperties.getExcludePaths()) {
            if(antPathMatcher.match(pathPattern, antPath)){
                return true;
            }
        }
        return false;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

重启测试,会发现 访问/items开头的路径,未登录状态下不会被拦截:​

在这里插入图片描述



访问其他路径则,未登录状态下请求会被拦截,并且返回401状态码:​

在这里插入图片描述

在这里插入图片描述

​2、拦截器获取用户

默认 扫不到,需要配置

在这里插入图片描述

在hm-common中已经有一个用于保存登录用户的ThreadLocal工具:

在这里插入图片描述

在这里插入图片描述

拦截器



package com.hmall.common.interceptor;

import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class UserInfoInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取请求头中的用户信息
        String userInfo = request.getHeader("user-info");
        // 2.判断是否为空
        if (StrUtil.isNotBlank(userInfo)) {
            // 不为空,保存到ThreadLocal
                UserContext.setUser(Long.valueOf(userInfo));
        }
        // 3.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户,防止内存泄露,一次请求结束后 释放内存
        UserContext.removeUser();
    }
}

配置 拦截器


package com.hmall.common.config;

import com.hmall.common.interceptors.UserInfoInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@ConditionalOnClass(DispatcherServlet.class) // 网关 不需要拦截,微服务 是基于SpringMVC的

public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInfoInterceptor());
    }
}

不过,需要注意的是,这个 配置类默认是不会生效的,因为它所在的包是com.hmall.common.config与其它微服务的扫描包不一致,无法被扫描到,因此无法生效。​

基于SpringBoot的自动装配原理,我们要将其添加到resources目录下的 META-INF/spring.factories 文件中:

在这里插入图片描述


org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.hmall.common.config.MyBatisConfig,\
  com.hmall.common.config.MvcConfig

3、 恢复 购物车代码

在这里插入图片描述

4、微服务之间传递 用户信息

在这里插入图片描述

下单的过程中,需要调用商品服务 扣减库存,调用购物车服务 清理用户购物车。而清理购物车时必须知道当前登录的用户身份

但是, 订单服务调用购物车时 并没有传递用户信息,购物车服务无法知道当前用户是谁!

因此 要想实现微服务之间的 用户信息传递,就必须在微服务发起调用时把用户信息存入请求头。

 //  所有由 OpenFeign发起的请求都会 先调用拦截器处理请求:

public interface RequestInterceptor {

  /**
   * Called for every request. 
   * Add data using methods on the supplied {@link RequestTemplate}.
   */
  void apply(RequestTemplate template);
}

在这里插入图片描述



@Configuration

public class DefaultFeignConfig {

    @Bean
    public RequestInterceptor userInfoRequestInterceptor() {
        return new RequestInterceptor () {
            @Override
            public void apply(RequestTemplate template) {
                // 获取登录用户
                Long userId = UserContext.getUser ();
                if (userId == null) {
                    // 如果为空则直接跳过
                    return;
                }
                // 如果不为空则放入请求头中,传递给下游微服务
                template.header ("user-info", userId.toString ());
            }
        };
    }
}

启动类 指明 feign的配置类


@SpringBootApplication
@EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DefaultFeignConfig.class)
public class MyServe {

}

网关 解决跨域问题

跨域问题

localhost:8090 访问 localhost:10010 端口不同,显然是跨域的请求。

解决

在gateway服务的application.yml 文件中,添加下面的配置:


spring:
  cloud:
    gateway:
      # 。。。
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求 
              - "http://localhost:8090"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期

配置管理

1、共享配置

2、配置热更新

在这里插入图片描述



@Component
@ConfigurationProperties(prefix = "hm.cart")
@Data
public class CartProperties {
    private Integer maxItems;

}

添加配置到Nacos

首先,我们在nacos中 添加一个配置文件,将 购物车的上限数量添加到配置中:

在这里插入图片描述

hm:
  cart:
    maxAmount: 1 # 购物车商品数量上限

在这里插入图片描述

提交配置,在 控制台能看到 新添加的配置:

在这里插入图片描述

微服务保护 (熔断、降级、限流)

在这里插入图片描述

1、 服务雪崩

在这里插入图片描述

2、限流 、熔断、降级

在这里插入图片描述

在这里插入图片描述

3、 服务保护技术

在这里插入图片描述

4、Sentinel 使用

https://blog.csdn.net/qq_30659573/article/details/127577889

分布式事务

在这里插入图片描述

1、Seata

https://blog.csdn.net/qq_30659573/article/details/127563258

2、本地消息表 + 定时任务扫表

在这里插入图片描述

3、MQ 分布式事务

4、最大努力通知

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值