文章目录
- 一、单体架构与微服务
-
- [1.1 单体架构](#1.1 单体架构)
- [1.2 集群与分布式架构](#1.2 集群与分布式架构)
- [1.3 什么是微服务架构](#1.3 什么是微服务架构)
- [1.4 微服务的优势与挑战](#1.4 微服务的优势与挑战)
- [二、Spring Cloud 到底是什么](#二、Spring Cloud 到底是什么)
-
- [2.1 Spring Cloud Netflix](#2.1 Spring Cloud Netflix)
- [2.2 Spring Cloud Alibaba](#2.2 Spring Cloud Alibaba)
- [2.3 组件对比](#2.3 组件对比)
- 三、工程搭建
-
- [3.1 微服务拆分原则](#3.1 微服务拆分原则)
- [3.2 数据准备](#3.2 数据准备)
-
- [3.2.1 订单服务数据库(cloud_order)](#3.2.1 订单服务数据库(cloud_order))
- [3.2.2 商品服务数据库(cloud_product)](#3.2.2 商品服务数据库(cloud_product))
- [3.3 父子工程搭建](#3.3 父子工程搭建)
-
- [3.3.1 创建父工程](#3.3.1 创建父工程)
- [3.3.2 dependencyManagement vs dependencies](#3.3.2 dependencyManagement vs dependencies)
- [3.4 创建子项目](#3.4 创建子项目)
-
- [3.4.1 订单服务(order-service)](#3.4.1 订单服务(order-service))
- [3.4.2 商品服务(product-service)](#3.4.2 商品服务(product-service))
- [3.5 RestTemplate远程调用](#3.5 RestTemplate远程调用)
-
- [第一步:注册RestTemplate Bean](#第一步:注册RestTemplate Bean)
- 第二步:修改OrderService,添加远程调用
- 四、什么是RESTful?
-
- REST核心概念
- [RESTful API特征](#RESTful API特征)
- 五、当前方案存在的问题
一、单体架构与微服务
1.1 单体架构
项目早期,创业公司或者传统业务系统,会把全部业务功能打包到一个 jar/war 包,所有模块共用一套数据库,这就是单体架构。
优点:开发简单、部署简单,模块之间不需要网络调用,适合业务初期、流量不大的系统。
缺点:
- 业务膨胀之后代码高度耦合;
- 修改任何一处逻辑,都需要整体重新构建、全量发布;
- 一个模块出 bug,会导致整个系统宕机;
- 用户流量上涨,服务器压力会越来越大,系统容易卡顿、无法访问。
1.2 集群与分布式架构
当业务规模变大,单体扛不住流量,我们会从两个方向优化:
-
横向扩容(集群):复制多台服务器,每台服务器都完整运行整套应用,通过负载均衡分发请求。
-
纵向拆分(分布式 / 垂直架构):把大项目按照业务拆分成多个独立子项目,部署在不同服务器,子项目互相配合完成完整业务。
集群 vs 分布式
- 集群:多台机器做一样的事情;每个节点功能等价,可以互相替代。
- 分布式:多台机器做不一样的事情;每个节点负责不同业务,一个节点故障,对应业务就不可用。
- 现实项目中二者经常搭配使用:分布式里面的某一个服务节点,本身就可以部署成集群,提高可用性。
1.3 什么是微服务架构
分布式架构按照业务拆分之后,会出现重复业务代码,多个系统都需要用户、订单这类通用能力。我们把通用业务抽出来,拆分成粒度更小、职责单一的独立服务,这就是微服务。
微服务特点:
- 一个服务只负责单一业务,只做一件事;
- 每个服务独立部署,拥有自己独立数据库;
- 服务之间通过 REST、RPC 进行远程通信;
- 微服务属于分布式架构的子集,是粒度更细、设计更规范的分布式方案。
分布式只是要求把系统拆开;微服务强调精细化拆分、专业化分工,分布式不一定是微服务,但微服务一定是分布式。
1.4 微服务的优势与挑战
微服务优势:
- 易维护:每个服务体量小,业务边界清晰,开发维护成本低;
- 故障隔离:单个服务故障,不会造成整个系统崩溃;
- 弹性扩展:可以针对压力大的服务单独扩容,不需要整体扩容;
- 技术灵活:不同服务可以根据业务选择最合适的技术栈。
拆成微服务不是没有代价,会带来一系列难题,这也是 Spring Cloud 要解决的:
- 服务依赖复杂:服务数量变多,服务调用链路错综复杂,改动一个服务要评估对其他服务的影响;
- 运维成本暴涨:大量服务需要编译、部署、监控,环境复杂;
- 开发测试难度提升:业务流程跨多个服务,网络延迟、网络异常都需要处理;
- 服务治理难题:服务注册发现、负载均衡、熔断降级、分布式配置、链路监控,都需要专门组件解决。
二、Spring Cloud 到底是什么
Spring Cloud 提供一套工具,帮助开发者快速实现分布式系统常见模式:配置管理、服务注册发现、路由、服务调用、负载均衡、熔断、分布式消息等能力。
Spring Cloud 是微服务分布式架构一站式解决方案,是一套规范,整合各类开源组件,基于 SpringBoot 封装,做到开箱即用。
Spring Cloud 本身不是框架,它定义微服务开发规范,整合第三方开源组件,屏蔽底层复杂实现,开发者只需要简单注解和配置就能使用分布式能力。
Spring Cloud 提供的能力:
- 分布式版本配置
- 服务注册与发现
- 网关路由
- 服务之间远程调用
- 负载均衡
- 断路器(熔断降级)
- 分布式消息
- 分布式链路追踪
Spring Cloud 只是一套规范,市面上有两套主流实现:Netflix 与 Alibaba
2.1 Spring Cloud Netflix
Netflix 公司开源组件,曾经是 Spring Cloud 默认实现。核心组件:
- Eureka:服务注册发现
- Zuul:网关
- Ribbon:客户端负载均衡
- Feign:声明式服务调用
- Hystrix:熔断降级
2018 年 Netflix 宣布 Hystrix、Ribbon、Zuul 进入维护模式,不再新增功能。Spring Cloud 官方给出替换方案:
2.2 Spring Cloud Alibaba
阿里开源实现,国内企业最常用微服务方案,吸收 Netflix 架构思想同时做大量性能优化。核心组件:
- Nacos:同时提供服务注册发现 + 分布式配置中心
- Sentinel:流量控制、熔断降级
- Seata:分布式事务
- Dubbo:高性能 RPC 调用
优势:中文文档完善、社区活跃,适配国内业务场景,现在已经成为国内微服务技术选型。
2.3 组件对比
| 能力 | Spring Cloud 官方 | Netflix | Spring Cloud Alibaba |
|---|---|---|---|
| 服务注册发现 | - | Eureka | Nacos |
| 服务调用 | OpenFeign | Feign | Dubbo |
| 配置中心 | Spring Cloud Config | Archaius | Nacos |
| 网关 | Spring Cloud Gateway | Zuul | Spring Cloud Gateway |
| 负载均衡 | Spring Cloud LoadBalancer | Ribbon | Dubbo 内置负载均衡 |
三、工程搭建
3.1 微服务拆分原则
-
单一职责原则:每个微服务应该只负责一个功能或业务领域,有清晰的定义和边界。这借鉴了面向对象设计中的单一职责原则------一个类只专注于单一功能。
-
服务自治 :每个微服务应具备高度自治能力:
- 独立开发、测试、构建、部署、运行
- 拥有独立的存储和配置
- 尽量减少对其他服务的依赖
-
单向依赖:服务之间应保持单向依赖,严禁循环依赖和双向依赖。如果某些场景确实无法避免,可以考虑使用消息队列等中间件解耦。
设计理念:合适优于业界领先,避免过度设计。罗马不是一天建成的,优秀的架构是在业务发展过程中逐步完善的。
3.2 数据准备
根据服务自治原则,每个服务拥有独立数据库。
3.2.1 订单服务数据库(cloud_order)
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);
3.2.2 商品服务数据库(cloud_product)
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),
(1010, '毛衣', 98, 0);
3.3 父子工程搭建
3.3.1 创建父工程
创建一个空的Maven项目,删除src目录,只保留pom.xml。
父工程POM配置
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">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring-cloud-demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.6</version>
</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>
<!-- 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>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- MyBatis测试 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>${mybatis.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
3.3.2 dependencyManagement vs dependencies
| 特性 | dependencies | dependencyManagement |
|---|---|---|
| 作用 | 直接引入jar包 | 声明依赖版本管理 |
| 子项目继承 | 自动继承 | 需要显式声明 |
| 版本指定 | 必须指定版本 | 子项目可不指定版本 |
| 适用场景 | 公共依赖(Lombok) | 版本统一管理 |
3.4 创建子项目
3.4.1 订单服务(order-service)
依赖配置(pom.xml)
xml
<dependencies>
<!-- Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MySQL驱动 -->
<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>
启动类
java
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);
}
}
配置文件(application.yml)
yaml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志
map-underscore-to-camel-case: true # 驼峰命名自动转换
业务代码
实体类 OrderInfo
java
import lombok.Data;
import java.util.Date;
@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;
}
Mapper
java
import com.bite.order.model.OrderInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface OrderMapper {
@Select("SELECT * FROM order_detail WHERE id=#{orderId}")
OrderInfo selectOrderById(Integer orderId);
}
Service
java
import com.bite.order.mapper.OrderMapper;
import com.bite.order.model.OrderInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
public OrderInfo selectOrderById(Integer orderId) {
return orderMapper.selectOrderById(orderId);
}
}
Controller
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/{orderId}")
public OrderInfo getOrderById(@PathVariable("orderId") Integer orderId) {
return orderService.selectOrderById(orderId);
}
}
3.4.2 商品服务(product-service)
商品服务结构与订单服务类似,端口改为9090 ,数据库使用cloud_product。
yaml
server:
port: 9090 # 不同端口避免冲突
3.5 RestTemplate远程调用
RestTemplate 是 Spring 封装的HTTP 客户端工具 ,可以使Java 后端程序去调用别的 HTTP 接口(服务之间调用、调用第三方 API)
需求:根据订单ID查询订单时,同时获取该订单中商品的详细信息。
第一步:注册RestTemplate Bean
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class BeanConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
第二步:修改OrderService,添加远程调用
java
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public OrderInfo selectOrderById(Integer orderId) {
// 1. 查询本地订单
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
// 2. 远程调用商品服务
String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
// 3. 组装数据
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
}
测试结果:访问 http://127.0.0.1:8080/order/1,返回结果包含订单信息和关联的商品信息。
四、什么是RESTful?
REST核心概念
- 资源:网络上的所有事物都可以抽象为资源,每个资源有唯一标识符(URI)
- 表现层:资源的表现形式,如JSON、XML、HTML等
- 状态转移:通过HTTP协议对资源进行操作(增删改查)
RESTful API特征
- 使用HTTP方法表示操作:GET(查询)、POST(创建)、PUT(修改)、DELETE(删除)
- 统一接口命名,资源通过URI定位,但是URI不体现操作,无法通过URI了解操作
- 无状态通信
RESTful风格虽然规范,但并非银弹:
- 实际业务可能复杂,不能简单用增删改查覆盖
- HTTP Method不可直观见,增加理解成本
- 部分浏览器对非GET/POST方法支持不友好
原则:方便团队开发和管理才是第一位,不要墨守成规。尽信书不如无书,尽信规范不如无规范。
五、当前方案存在的问题
通过RestTemplate实现远程调用后,我们发现了几个问题:
| 问题 | 描述 |
|---|---|
| 硬编码IP/端口 | URL中写死127.0.0.1:9090,更换地址需修改代码 |
| 无法负载均衡 | 多机部署时无法分摊压力 |
| 易出错 | URL拼接容易写错,复用性差 |
| 无权限控制 | 所有服务都可随意调用,存在安全风险 |
| 无服务发现 | 调用方依赖服务提供方的具体地址 |
这些问题将在后续的Spring Cloud学习中,通过服务注册与发现(Nacos) 、负载均衡(Ribbon/LoadBalancer) 、服务调用(OpenFeign) 、网关(Gateway) 等组件逐步解决。