微服务拆分以及注册中心

目录

一、补充完成昨天未完成的虚拟机搭建

二、关于黑马商城项目的微服务拆分

微服务拆分的两种方式:

正式拆解商品服务项目:

商品服务:

购物车服务:

服务调用:

三、Nacos注册中心


一、补充完成昨天未完成的虚拟机搭建

首先将资料当中的镜像导入到VMWare当中并配置网络,启动虚拟机输入密码后打开finalshell进行连接:

效果展示:

途中遇到的问题:

1.刚刚加入镜像启动虚拟机报错:

虚拟机使用的是此版本 VMware Workstation 不支持的硬件版本。 模块"Upgrade"启动失败。 未能启动虚拟机。

右键单击虚拟机 -> 管理 -> 更改硬件兼容性 -> 将按向导操作完成更改;这样就可以完成虚拟机启动;

2.启动虚拟机后发现finalshell连接不上,原来是虚拟机这里没有更改网络配置;单击编辑-虚拟网络编辑器-更改设置-将NAT网段的子网修改成196.128.150.0即可

二、关于黑马商城项目的微服务拆分

通过研读登录、查询、购物车、下单、支付的代码了解项目结构:

登录入口在com.hmall.controller.UserController中的login方法:

这个是老生常谈的一个功能了,不过多赘述

在首页搜索框输入关键字,点击搜索即可进入搜索列表页面:

该页面会调用接口:/search/list,对应的服务端入口在com.hmall.controller.SearchController中的search方法:

这里用到了MP(Mybatis-Plus),like、eq、between用来存储查询条件,page方法用来分页,这里PageDTO .of 是一个封装好的方法,用以返回总记录数、页面总数、商品列表

在搜索到的商品列表中,点击按钮加入购物车,即可将商品加入购物车:

加入成功后即可进入购物车列表页,查看自己购物车商品列表:

同时这里还可以对购物车实现修改、删除等操作。

相关功能全部在com.hmall.controller.CartController中:

在购物车页面点击结算按钮,会进入订单结算页面:

点击提交订单,会提交请求到服务端,服务端做3件事情:

  • 创建一个新的订单

  • 扣减商品库存

  • 清理购物车中商品

业务入口在com.hmall.controller.OrderController中的createOrder方法:

下单完成后会跳转到支付页面,目前只支持余额支付

在选择余额支付这种方式后,会发起请求到服务端,服务端会立刻创建一个支付流水单,并返回支付流水单号到前端。

当用户输入用户密码,然后点击确认支付时,页面会发送请求到服务端,而服务端会做几件事情:

  • 校验用户密码

  • 扣减余额

  • 修改支付流水状态

  • 修改交易订单状态

请求入口在com.hmall.controller.PayController中:

微服务拆分的两种方式:

1.纵向拆分:所谓纵向拆分,就是按照项目的功能模块来拆分。例如黑马商城中,就有用户管理功能、订单管理功能、购物车功能、商品管理功能、支付功能等。那么按照功能模块将他们拆分为一个个服务,就属于纵向拆分。这种拆分模式可以尽可能提高服务的内聚性。

2.横向拆分:而横向拆分,是看各个功能模块之间有没有公共的业务部分,如果有将其抽取出来作为通用服务。例如用户登录是需要发送消息通知,记录风控数据,下单时也要发送短信,记录风控数据。因此消息发送、风控数据记录就是通用的业务功能,因此可以将他们分别抽取为公共服务:消息中心服务、风控管理服务。这样可以提高业务的复用性,避免重复开发。同时通用业务一般接口稳定性较强,也不会使服务之间过分耦合。

正式拆解商品服务项目:

商品服务:

1.在hmall工程下新建maven模块item-service,jdk选择11,选择java语言、maven项目,不需要示例代码,直接创建;

2.修改pom.xml文件:

将父工程下的依赖复制过来并且把不需要的删除掉:

XML 复制代码
<?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">
    <parent>
        <artifactId>hmall</artifactId>
        <groupId>com.heima</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>item-service</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    <dependencies>
        <!--common-->
        <dependency>
            <groupId>com.heima</groupId>
            <artifactId>hm-common</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3.编写启动类:

注意@MapperScan注解的值要加上item,这样才能扫描到

java 复制代码
package com.hmall.item;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan("com.hmall.item.mapper")
@SpringBootApplication
public class ItemApplication {
    public static void main(String[] args) {
        SpringApplication.run(ItemApplication.class, args);
    }
}

4.接下来是配置文件,可以从hm-service中拷贝:

其中,application.yaml内容需要修改:

javascript 复制代码
server:
  port: 8081
spring:
  application:
    name: item-service
  profiles:
    active: dev
  datasource:
    url: jdbc:mysql://${hm.db.host}:3306/hm-item?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: ${hm.db.pw}
mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
  global-config:
    db-config:
      update-strategy: not_null
      id-type: auto
logging:
  level:
    com.hmall: debug
  pattern:
    dateformat: HH:mm:ss:SSS
  file:
    path: "logs/${spring.application.name}"
knife4j:
  enable: true
  openapi:
    title: 黑马商城商品管理接口文档
    description: "黑马商城商品管理接口文档"
    email: zhanghuyi@itcast.cn
    concat: 虎哥
    url: https://www.itcast.cn
    version: v1.0.0
    group:
      default:
        group-name: default
        api-rule: package
        api-rule-resources:
          - com.hmall.item.controller

5.然后拷贝hm-service中与商品管理有关的代码到item-service,如图:

这里有一个地方的代码需要改动,就是ItemServiceImpl中的deductStock方法:

6.导入数据库表。默认的数据库连接的是虚拟机,在你docker数据库执行课前资料提供的SQL文件:

连接数据库后运行sql脚本hm-item.sql即可

最终,会在数据库创建一个名为hm-item的database,将来的每一个微服务都会有自己的一个database:

7.接下来,就可以启动测试了,在启动前我们要配置一下启动项,让默认激活的配置为local而不是dev

在打开的编辑框填写active profiles:

8.接着,启动item-service,访问商品微服务的swagger接口文档:http://localhost:8081/doc.html

然后测试其中的根据id批量查询商品这个接口:

效果展示:

中途遇到的问题,首先我们把代码复制下来,由于包的位置变化了,所以原先的导包语句全都报错,这个时候需要删除导包语句,idea会自动导包;其次就是新增包的时候包名写成了hmal,少了个l,这导致启动类找不到,将包名更正后完成启动

购物车服务:

1.与商品服务类似,在hmall下创建一个新的module,起名为cart-service

2.然后是依赖:

XML 复制代码
<?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">
    <parent>
        <artifactId>hmall</artifactId>
        <groupId>com.heima</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cart-service</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <!--common-->
        <dependency>
            <groupId>com.heima</groupId>
            <artifactId>hm-common</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3.然后是启动类:

java 复制代码
package com.hmall.cart;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

4.然后是配置文件,同样可以拷贝自item-service,不过其中的application.yaml需要修改:

javascript 复制代码
server:
  port: 8082
spring:
  application:
    name: cart-service
  profiles:
    active: dev
  datasource:
    url: jdbc:mysql://${hm.db.host}:3306/hm-cart?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: ${hm.db.pw}
mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
  global-config:
    db-config:
      update-strategy: not_null
      id-type: auto
logging:
  level:
    com.hmall: debug
  pattern:
    dateformat: HH:mm:ss:SSS
  file:
    path: "logs/${spring.application.name}"
knife4j:
  enable: true
  openapi:
    title: 黑马商城购物车接口文档
    description: "黑马商城购物车接口文档"
    email: zhanghuyi@itcast.cn
    concat: 虎哥
    url: https://www.itcast.cn
    version: v1.0.0
    group:
      default:
        group-name: default
        api-rule: package
        api-rule-resources:
          - com.hmall.cart.controller

5.最后,把hm-service中的与购物车有关功能拷贝过来,最终的项目结构如下:

特别注意的是com.hmall.cart.service.impl.CartServiceImpl,其中有两个地方需要处理:

  • 需要获取登录用户信息,但登录校验功能目前没有复制过来,先写死固定用户id

  • 查询购物车时需要查询商品信息,而商品信息不在当前服务,需要先将这部分代码注释

java 复制代码
    @Override
    public List<CartVO> queryMyCarts() {
        // 1.查询我的购物车列表
        List<Cart> carts = lambdaQuery().eq(Cart::getUserId, 1L /*TODO UserContext.getUser()*/).list();
        if (CollUtils.isEmpty(carts)) {
            return CollUtils.emptyList();
        }
        // 2.转换VO
        List<CartVO> vos = BeanUtils.copyList(carts, CartVO.class);
        // 3.处理VO中的商品信息
        handleCartItems(vos);
        // 4.返回
        return vos;
    }

    private void handleCartItems(List<CartVO> vos) {
        // 1.获取商品id TODO 处理商品信息
        /*Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
        // 2.查询商品
        List<ItemDTO> items = itemService.queryItemByIds(itemIds);
        if (CollUtils.isEmpty(items)) {
            throw new BadRequestException("购物车中商品不存在!");
        }
        // 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.最后,还是要导入数据库表,在本地数据库直接执行课前资料对应的SQL文件:

在数据库中会出现名为hm-cartdatabase,以及其中的cart表,代表购物车:

7.接下来,就可以测试了。不过在启动前,同样要配置启动项的active profilelocal

然后启动CartApplication,访问swagger文档页面:http://localhost:8082/doc.html

我们测试其中的查询我的购物车列表接口:

效果展示:

服务调用:

在拆分的时候,我们发现一个问题:就是购物车业务中需要查询商品信息,但商品信息查询的逻辑全部迁移到了item-service服务,导致我们无法查询。

最终结果就是查询到的购物车数据不完整,因此要想解决这个问题,我们就必须改造其中的代码,把原本本地方法调用,改造成跨微服务的远程调用(RPC,即R emote P roduce Call)。

因此,现在查询购物车列表的流程变成了这样:

代码中需要变化的就是这一步:

Spring给我们提供了一个RestTemplate的API,可以方便的实现Http请求的发送。其中提供了大量的方法,方便我们发送Http请求,例如:

1.我们在cart-service服务中定义一个配置类:

先将RestTemplate注册为一个Bean:

java 复制代码
package com.hmall.cart.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RemoteCallConfig {

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

2.接下来,我们修改cart-service中的com.hmall.cart.service.impl.CartServiceImplhandleCartItems方法,发送http请求到item-service

java 复制代码
private final RestTemplate restTemplate;

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发送http请求与前端ajax发送请求非常相似,都包含四部分信息:

  • ① 请求方式

  • ② 请求路径

  • ③ 请求参数

  • ④ 返回值类型

这里值得注意的是,Spring其实是不推荐用@Autowired注解进行注入的,而推荐用构造器注入,但是传统的构造器注入代码繁杂,属性一多篇幅就庞大起来了,所以这里介绍一个@RequiredArgsConstructor注解,其可以让被final修饰的属性完成构造器注入,比如此处的restTemplate

效果展示:

这里碰到一个问题就是,发送测试请求的时候发现返回500,调查发现8081端口的item-service被我关掉了...
服务调用也带来了两个问题:我在调用的时候已经把url写死了,等到部署的时候,万一部署的地址不是我目前这个url或者我有多个item-service服务器,这时怎么办呢?

这里引入注册服务中心:这里面有三个角色,分别是服务提供者、服务消费者、注册中心;而原先的服务调用就被我们舍弃,转而用服务治理-注册中心

三、Nacos注册中心

目前开源的注册中心框架有很多,国内比较常见的有:

  • Eureka:Netflix公司出品,目前被集成在SpringCloud当中,一般用于Java应用

  • Nacos:Alibaba公司出品,目前被集成在SpringCloudAlibaba中,一般用于Java应用

  • Consul:HashiCorp公司出品,目前集成在SpringCloud中,不限制微服务语言

以上几种注册中心都遵循SpringCloud中的API规范,因此在业务开发使用上没有太大差异。由于Nacos是国内产品,中文文档比较丰富,而且同时具备配置管理功能(后面会学习),因此在国内使用较多,课堂中我们会Nacos为例来学习。

我们基于Docker来部署Nacos的注册中心,首先我们要准备MySQL数据库表,用来存储Nacos的数据。由于是Docker部署,所以大家需要将资料中的SQL文件导入到你Docker中的MySQL容器中:

然后,找到课前资料下的nacos文件夹,其中的nacos/custom.env文件中,有一个MYSQL_SERVICE_HOST也就是mysql地址,需要修改为你自己的虚拟机IP地址,然后,将课前资料中的nacos目录上传至虚拟机的/root目录。进入root目录,然后执行下面的docker命令:

bash 复制代码
docker run -d \
--name nacos \
--env-file ./nacos/custom.env \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--restart=always \
nacos/nacos-server:v2.1.0-slim

启动完成后,访问下面地址:http://192.168.150.101:8848/nacos/,注意将192.168.150.101替换为你自己的虚拟机IP地址。

效果展示:

相关推荐
算力魔方AIPC18 小时前
使用 Docker 一键部署 PaddleOCR-VL: 新手保姆级教程
运维·docker·容器
Evan芙18 小时前
nginx核心配置总结,并实现nginx多虚拟主机
运维·数据库·nginx
FIT2CLOUD飞致云18 小时前
操作教程丨通过1Panel快速安装Zabbix,搭建企业级监控系统
运维·服务器·开源·zabbix·监控·1panel
幸存者letp18 小时前
Python 常用方法分类大全
linux·服务器·python
知识分享小能手19 小时前
Ubuntu入门学习教程,从入门到精通,Linux操作系统概述(1)
linux·学习·ubuntu
KnowFlow企业知识库19 小时前
KnowFlow v2.3.0 重磅发布:适配 RAGFlow v0.22.1 和 MinerU v2.6.5、新增支持多模态视频解析,让知识库"看见"更多
linux·github
悟空空心19 小时前
服务器长ping,traceroute
linux·服务器·网络·ssh·ip·ping++
Ghost Face...19 小时前
Docker实战:从安装到多容器编排指南
运维·docker·容器
此生只爱蛋19 小时前
【Linux】正/反向代理
linux·运维·服务器
qq_54702617919 小时前
Linux 基础
linux·运维·arm开发