SpringCloud —— 黑马商城的项目拆分和Nacos

一、前言

我们已经将黑马商城的前置知识学完(mp、docker),接下来就是要开始黑马商城项目了,这个项目本身是一个已经做好的项目,但是是单体架构,和苍穹外卖是一样的,现在我们将利用黑马商城来学习微服务,所以首先就需要拆分项目了。

在这之前,记得一定要创建一个git仓库,并且虚拟机要定期保存快照,本项目经多人实测,中途可能会出现问题(比如无法访问nacos网站等问题),而本人也经历了虚拟机崩溃的问题,所以在将来可能需要使用git回溯,如果虚拟机出问题了也需要恢复快照。

二、项目结构和拆分原则

黑马商城的项目结构是比较简单的,每一个板块都是分得比较清楚的,比如有商品模块、购物车模块、订单模块、支付模块等等......

这里我们选择将这些较为独立集中的模块拆分成项目结构中的模块(也可以直接拆成一个独立的项目,但是在学习期间不易管理),如下图所示(部分拆分)

同时需要导入不同模块所需要的表:

我们的拆分原则是:拆分出功能相对独立的模块,每一个拆分后的模块需要有自己单独的表。

三、远程调用

在拆分时我们会发现一些问题,比如购物车模块需要依赖商品模块,但是跨模块是不允许直接调用的,而且也不能直接调用,因为我们拆分项目的初衷是解耦合,使项目更好管理,如果模块之间相互依赖调用,这样和单体架构就没什么两样了,所以也就完全体现不出微服务的优势了。

所以这里我们绝对不能直接调用,这个时候就可以联想到之前苍穹外卖使使用过的HttpClient了,当时我们是使用这个工具来让客户端获取店铺信息,本质上是通过向管理端发起了一个请求,然后获取管理端传回来的响应,解析后拿到店铺状态。

这里我们暂时就不使用httpclient了,我们使用restTemplate:

首先需要注册到Bean中去,所以要一个配置类,但是由于这个配置很简单,所以我们直接写在启动类中:

java 复制代码
@MapperScan("com.hmall.cart.mapper")
@SpringBootApplication
public class CartApplication {
    public static void main(String[] args) {
        SpringApplication.run(CartApplication.class, args);
    }

    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

使用restTemplate主要也就分为两步:

1.发送请求并接收响应

2.解析响应

java 复制代码
private void handleCartItems(List<CartVO> vos) {
    // TODO 1.获取商品id
    Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
    // 2.查询商品
    // List<ItemDTO> items = itemService.queryItemByIds(itemIds);
    // 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());
    }
}

虽然逻辑简单,但是代码确实不少,太繁琐了,所以后期我们会使用一个新技术来高度封装这些步骤,这里学习RestTemplate有助于了解封装的底层原理,所以还是需要看看的。

四、Nacos注册中心

1.简介

首先我们要知道,目前我们拆分了项目,但是其实是没有进行统一管理的,尤其是一个模块如果出现多个实例,我们根本无法达到负载均衡。

先前我们在nginx中提到了负载均衡的,大致意思就是如果一个模块是一个服务器集群,那么当多个请求访问这个服务器时,可以将请求压力分摊到每个服务器上,也就是不会一直只访问一个服务器,这就是负载均衡

为了实现这个目的,我们要引入一个管理中心------Nacos,我们可以将每个模块注册进这个管理中心,由Nacos管理,有远程调用的需求时,Nacos会自动将请求分配给目标服务器(告诉请求发起者应该请求给哪个服务器)

2.具体实现步骤

(1)创建并启动容器

首先我们要在虚拟机的Docker中用Nacos的镜像创建一个Nacos的容器:

(2)在目标模块导入依赖

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

(3)写入配置文件

XML 复制代码
  cloud:
    nacos:
      server-addr: 192.168.xxx.xxx:8848

这里就是指定Nacos容器所在的IP地址和Nacos的端口号,这样就能让SpringCloud将项目注册到Nacos中了(导入依赖的目的就是为了让这个配置生效)。

(4)测试

这里我将启动三个模块,其中有两个实例是源于一个模块,相当于模拟一个服务器集群(集群中有两台服务器)

这是就可以访问nacos的网站了,在这里就可以统一管理服务器了:

这里我们发送五次请求,看看是否实现了负载均衡:

从结果来看是实现了的,8081和8083都是被请求到了的。

五、OpenFeign

刚刚在远程调用的时候就提到了,使用restTemplate是及其不方便的,也预告了后面会封装,这个组件就是OpenFeign,它通过和SpringBoot相似的语法,减少了学习成本,让远程调用更加规范,耦合度更低,最重要的是,代码量大幅减少。

OpenFeign的底层是可以选择的,比如刚刚提到的httpclient,除此之外还要okhttp等等。

接下来讲解如何使用OpenFeign:

1.启动类

首先启动类需要开启OpenFeign:

java 复制代码
@EnableFeignClients
@MapperScan("com.hmall.cart.mapper")
@SpringBootApplication
public class CartApplication {
    public static void main(String[] args) {
        SpringApplication.run(CartApplication.class, args);
    }
}

2.导入依赖

XML 复制代码
        <!--openFeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        </dependency>
        <!--OK http 的依赖 -->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
        </dependency>

3.配置

XML 复制代码
feign:
  okhttp:
    enabled: true # 开启OKHttp功能

4.创建接口

这里注意,这个接口是不需要被实现的,和Mapper接口一样,Spring会通过动态代理自动实现。

java 复制代码
@FeignClient("item-service")
public interface ItemClient {
    @GetMapping("/items")
    List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}

可以看到这个接口的写法和SpringBoot的语法几乎一样,所以也不再赘述,就是需要注意要在注解写明远程调用的项目名

5.远程调用

修改代码,一行代码就可以获取到item-service响应回来的数据了。

java 复制代码
private void handleCartItems(List<CartVO> vos) {
        //1.获取商品id
        Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());

        //openfeign
        List<ItemDTO> items = itemClient.queryItemByIds(itemIds);
        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());
        }
    }

6.日志级别

在这之前,我们创建了一个新的模块:hm-api,这里面包含了许多需要复用的dto以及feignClient,接下来我们来看看怎么开启feign的日志输出:

(1)创建配置类

在api模块创建配置类,因为这个日志输出的配置是需要复用的。

java 复制代码
public class DefaultFeignConfig {

    /**
     * 配置日志级别
     * @return
     */
    @Bean
    public Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

(2)启动类

第一、需要扫描api的包(通过依赖导入的),第二,直接指定日志输出级别配置类的字节码,用于配置日志输出:

java 复制代码
@EnableFeignClients(basePackages = "com.hmall.api.client",defaultConfiguration = DefaultFeignConfig.class)
@MapperScan("com.hmall.cart.mapper")
@SpringBootApplication
public class CartApplication {
    public static void main(String[] args) {
        SpringApplication.run(CartApplication.class, args);
    }

    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

测试后就可以看到输出的日志了:

相关推荐
卜锦元2 小时前
Golang中make()和new()的区别与作用?
开发语言·后端·golang
疯狂的程序猴2 小时前
iOS 应用保护工具怎么选?从攻击面拆解到工具职责划分的全链路实战指南
后端
中文很快乐2 小时前
从零到一:用 SpringBoot 打造 RESTful API 实战指南
java·spring boot·后端·restful
泉城老铁2 小时前
springboot+redis 如何实现订单的过期
java·后端·架构
哈哈哈笑什么2 小时前
在高并发分布式SpringCloud系统中,什么时候时候并行查询,提高查询接口效率,从10s到100ms
java·分布式·后端
Java水解2 小时前
Django实现接口token检测的实现方案
后端·django
南雨北斗2 小时前
kotlin密封类的主要用途
后端
泉城老铁2 小时前
如何用Spring Boot实现分布式锁?
java·redis·后端
飞Link2 小时前
【Django】Django 调用外部 Python 程序的完整指南
后端·python·django·sqlite