【微服务】(2) 环境和工程搭建

先安装好 JDK 和 MySQL。

一、案例介绍

以电商平台为例,有很多业务:用户、订单、秒杀、搜索......该服务巨大,微服务是更好的选择。

1、服务拆分原则

并不是越细越好,越细带来的管理困难也就越高。

  • 单一职责,效率更高。
  • 服务自治,互相影响更小。每个服务都能独立开发、测试、构建、部署、运行。
  • 单向依赖,禁止循环、双向依赖。无法避免时,可用其他技术如消息队列解耦。

2、服务拆分示例

对于订单列表:

① 单一职责:拆分为订单服务、商品服务(实际项目划分更细)。

② 服务自治:每个服务创建独立数据库。

订单表:

sql 复制代码
-- 建库
create database if not exists cloud_order charset utf8mb4;
-- 订单表
DROP TABLE IF EXISTS order_detail;
CREATE TABLE order_detail (
 `id` INT NOT NULL AUTO_INCREMENT COMMENT '订单id',
 `user_id` BIGINT ( 20 ) NOT NULL COMMENT '⽤⼾ID',
 `product_id` BIGINT ( 20 ) NULL COMMENT '产品id',
 `num` INT ( 10 ) NULL DEFAULT 0 COMMENT '下单数量',
 `price` BIGINT ( 20 ) NOT NULL COMMENT '实付款',
 `delete_flag` TINYINT ( 4 ) NULL DEFAULT 0,
 `create_time` DATETIME DEFAULT now(),
 `update_time` DATETIME DEFAULT now(),
PRIMARY KEY ( id )) ENGINE = INNODB DEFAULT CHARACTER
SET = utf8mb4 COMMENT = '订单表';
-- 数据初始化
insert into order_detail (user_id,product_id,num,price)
values
(2001, 1001,1,99), (2002, 1002,1,30), (2001, 1003,1,40),
(2003, 1004,3,58), (2004, 1005,7,85), (2005, 1006,7,94);

商品表:

sql 复制代码
create database if not exists cloud_product charset utf8mb4;
-- 产品表
DROP TABLE IF EXISTS product_detail;
CREATE TABLE product_detail (
 `id` INT NOT NULL AUTO_INCREMENT COMMENT '产品id',
 `product_name` varchar ( 128 ) NULL COMMENT '产品名称',
 `product_price` BIGINT ( 20 ) NOT NULL COMMENT '产品价格',
 `state` TINYINT ( 4 ) NULL DEFAULT 0 COMMENT '产品状态 0-有效 1-下架',
 `create_time` DATETIME DEFAULT now(),
 `update_time` DATETIME DEFAULT now(),
PRIMARY KEY ( id )) ENGINE = INNODB DEFAULT CHARACTER
SET = utf8mb4 COMMENT = '产品表';
-- 数据初始化
insert into product_detail (id, product_name,product_price,state)
values
(1001,"T恤", 101, 0), (1002, "短袖",30, 0), (1003, "短裤",44, 0), 
(1004, "卫⾐",58, 0), (1005, "⻢甲",98, 0),(1006,"⽻绒服", 101, 0), 
(1007, "冲锋⾐",30, 0), (1008, "袜⼦",44, 0), (1009, "鞋⼦",58, 0),
(10010, "⽑⾐",98, 0)

可能会出现错误:

这是客户端编码跟表编码不一致的锅,在建库前加一句:

sql 复制代码
SET NAMES utf8mb4;

二、工程搭建

1、构建父子工程

(1)构建父项目

  • 父工程负责管理子模块依赖,不负责编译生成可执行文件,因此只需保留 pom.xml 文件。

创建空 maven 项目,删除所有源代码,只保留 pom.xml 文件

完善 pom 文件:

  • 父工程的打包方式是 pom 而不是 jar,因此需要手动声明以 pom 方式打包
  • 父工程依赖 Sring Boot 父工程,需要 <parent> 声明父工程
  • <properties> 管理依赖的版本号,注意 spring cloud 版本与 spring boot 版本匹配。
  • <dependencies> jar 包直接放到项目中,子项目直接继承
  • <dependencyManagement> 声明依赖 ,没有引入 jar 包,子项目想引入 jar 需要显示声明 。若子项目指定版本号,按指定版本引入;若子项目没有指定版本按父工程管理的版本号来。
XML 复制代码
    <!-- 声明当前工程包含哪些子工程,创建子工程时会自动添加到当前工程中-->
    <modules>
        <module>order-service</module>
        <module>product-service</module>
    </modules>

    <!-- 声明以 pom 方式打包 -->
    <packaging>pom</packaging>

    <!-- 继承官方的 Spring Boot 父工程,引入 Spring Boot 常用依赖无需手动指定版本号-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.6</version>
        <relativePath/>
    </parent>

    <!-- 声明管理依赖的版本号-->
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <java.version>17</java.version>
        <mybatis.version>3.0.3</mybatis.version>
        <mysql.version>8.0.33</mysql.version>
        <spring-cloud.version>2022.0.3</spring-cloud.version>
    </properties>

    <!-- 引入依赖,子工程继承父工程的依赖,无需重复引入-->
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <!-- 声明依赖,子工程引入依赖时,可无需指定版本号,由父工程统一管理-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.version}</version>
            </dependency>
            <dependency>
                <groupId>com.mysql</groupId>
                <artifactId>mysql-connector-j</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter-test</artifactId>
                <version>${mybatis.version}</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

因为**<dependencyManagement>** 只是声明,没有真正引入 jar 包,所以爆红了很正常,不用管。上面的依赖没有爆红,是因为我之前下载到本地仓库过:

(2)构建子项目:订单、商品服务

创建新模块:

引入项目依赖和项目构建插件,这里就不用指定版本号,直接继承父工程的依赖版本号管理:

XML 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

2、完善子项目服务

  • 启动类
java 复制代码
package com.pygymi.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}
  • 配置文件
java 复制代码
#应用端口号
server:
  port: 8080
# 数据库连接配置
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: "123456"
    driver-class-name: com.mysql.jdbc.Driver
# mybatis配置
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印日志
    map-underscore-to-camel-case: true # 配置驼峰⾃动转换
  • 完善订单服务业务代码:

实体类:

java 复制代码
@Data
public class OrderInfo {
    private Integer id;
    private Integer userId;
    private Integer productId;
    private Integer num;
    private Integer price;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
}

controller:

java 复制代码
@RestController
@RequestMapping("/order")
public class OrderController {
    @Resource(name = "orderServiceImpl")
    private OrderService orderService;
    
    @GetMapping("/{orderId}")
    public OrderInfo getOrder(@PathVariable("orderId") String orderId) {
        return orderService.getOrder(orderId);
    }
}

service:

java 复制代码
public class OrderServiceImpl implements OrderService {
    @Resource
    private OrderMapper orderMapper;

    @Override
    public OrderInfo getOrder(String orderId) {
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
        return orderInfo;
    }
}

mapper:

java 复制代码
public interface OrderMapper {
    @Select("SELECT * FROM order_info WHERE id = #{orderId}")
    OrderInfo selectOrderById(String orderId);
}
  • 完善商品服务业务代码:同理。
  • 接口测试:

订单查询:127.0.0.1:8080/order/1

商品查询:127.0.0.1:9090/product/1001

3、远程调用

查询订单信息时,需要查询商品的详细信息,解决办法是:订单服务通过 HTTP 访问商品服务,获取到商品详情放到订单返回结果中。

实现方式使用 Spring 提供的 RestTemplate。

定义 RestTemplate,因为五大注入 bean 的注解修饰的是类,RestTemplate 是别人写的类我们无法修改,所以使用方法注入 @Bean:

java 复制代码
@Configuration
public class BeanConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

在订单服务中,通过 HTTP 访问商品服务:

java 复制代码
@Service
public class OrderServiceImpl implements OrderService {
    @Resource
    private OrderMapper orderMapper;
    @Resource
    private RestTemplate restTemplate;

    @Override
    public OrderInfo getOrder(String orderId) {
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
        // 构造访问商品服务的 url
        String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();
        // 使用 restTemplate 访问商品服务
        ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
        // 把商品详情设置到 orderInfo 中
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }
}

测试:127.0.0.1:8080/order/1

三、RestTemplate

1、REST

REST(表现层资源状态转移)是一种设计风格,指网络资源以某种表现形式进行状态转移。

  • 资源:网络上的所有数据,如图像、视频、文本等。
  • 表现层:资源的表现形式,如 jpg、txt、json 等。
  • 状态转移:通过网络访问资源,对资源进行修改(增删改查),都会引起资源状态转移。

2、RESTful API

满足 REST 设计风格的接口都叫做 RESTful API。其特性:

  • 统一接口:对资源的操作,对应 HTTP 协议提供的 GET、POST、PUT、DELETE。

如:GET /blog/{blogId}:查询博客

使用 REST 风格实现的接口,我们只能从 URL 定位其资源,并不能直接知晓其是什么操作,想要知道还需要用抓包工具查看请求,得知 GET、POST......

3、RestTemplate

就是 Spring 实现的,强制使用 RESTsul 风格的 HTTP 调用模板。我们只需要提供资源地址和参数类型。

4、RESTful 风格缺点

  • 想知道该接口对资源执行了什么操作,还要先抓包,不方便团队理解和交流。
  • 一些旧浏览器对 GET、POST 以外的请求支持不太友好。
  • 复杂的业务强行使用 RESTful 风格反而增加开发难度,因为不能仅仅使用增删改查实现。

四、项目存在的问题

  • 远程调用时,被调用方的 IP 是写死的,IP 容易写错,也容易在源代码基础上修改。我们希望不依赖提供方的 IP。
  • 所有服务都可以调用该接口,具有风险。
  • 多机部署,没有实现压力分摊。

...... 后续继续学习 spring cloud 的各种组件,来解决这些问题。

相关推荐
xrkhy4 小时前
微服务之配置中心Nacos
微服务·架构
xrkhy4 小时前
微服务之Gateway网关(1)
微服务·架构·gateway
喵叔哟4 小时前
62.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--新增功能--自训练ML模型
微服务·架构·.net
qq_264220896 小时前
k8s-Pod详解
云原生·容器·kubernetes
小诸葛的博客6 小时前
k8s localpath csi原理
云原生·容器·kubernetes
小猿姐11 小时前
闲谈KubeBlocks For MongoDB设计实现
mongodb·云原生·kubernetes
勤源科技12 小时前
全链路智能运维中的实时流处理架构与状态管理技术
运维·架构
JanelSirry13 小时前
SOA和微服务之间的主要区别是什么
微服务·soa
失散1313 小时前
分布式专题——43 ElasticSearch概述
java·分布式·elasticsearch·架构