目录
1. 什么是 Spring Cloud
Spring Cloud 是分布式微服务架构的一站式解决方案,是微服务架构的各种技术的集合
例如:
1)Distributed/versioned configuration 分布式版本配置
2)Service registration and discy 服务注册和发现
3)Routing 路由
4)Service-to-service calls 服务调用
5)Load balancing 负载均衡
6)Circuit Breakers 熔断器
7)Distributed messaging 分布式消息
2. 案例介绍
2.1 需求
实现一个电商平台,在一个电商平台中包含非常多的内容,非常多的功能,如果把这些功能全部写在一个服务里,这个服务将是非常巨大的,微服务架构是最高的选择,微服务应用开发的第一步就是服务拆分,拆分后才能进行"各自开发"
2.2 服务拆分
服务拆分原则
微服务并不是越小越号,服务越少,微服务架构的优点和缺点会越来越明显,服务越小,微服务的独立性就会越来越高,同时,微服务的数量也会越来越多,管理这些微服务的难度也会提高
1)单一职责原则
单一职责原则原本是面向对象设计中的一个基本原则,它指的是一个类应该专注于单一功能,不要存在多于一个导致类变更的原因
在微服务架构中,一个微服务也应该只负责一个功能或者业务领域,每个服务应该只关注自己的特定业务领域
例如电商平台
2)服务自治
服务自治是指每个微服务都应该具备高度自治的能力,即每个服务要做到独立开发、独立测试、独立构建、独立部署、独立运行
一上面的电商系统为例,每一个微服务都应该有自己的存储,配置,在进行开发,构建,部署,运行和测试时,不需要过多关注其他微服务的状态和数据
3)单项依赖
微服务之间需要做到单向依赖,严禁循环依赖,双向依赖
循环依赖:A — B — C — A
双向依赖:A — B,B — A
服务拆分示例
以订单列表为例:
例如提供了订单列表和商品信息,根据服务的单一职责。把服务进行拆分成:订单服务、商品服务
订单服务:提供订单 ID,获取订单详情信息
商品服务:根据商品 ID,返回商品详情信息
3. 数据准备
根据服务自治原则,每个服务都应该有自己独立的数据库
订单服务:
-- 订单服务
-- 建库
create database if not exists cloud_order charset utf8mb4;
use cloud_order;
-- 订单表
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);
-- 产品服务
create database if not exists cloud_product charset utf8mb4;
-- 产品表
use cloud_product;
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);
4. 工程搭建
4.1 构建父子工程
4.1.1 创建父工程
1)创建一个空的 Maven 项目,删除所有代码,只保留 pom.xml
目录结构
2)完善 pom 文件
使用 properties 来进行版本号的统一管理,使用 dependencyManagement 来管理依赖,声明父工程的打包方式为 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>org.example</groupId>
<artifactId>spring-cloud-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.6</version>
<relativePath/> <!-- lookup parent from repository -->
</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>
</project>
DependencyManagement 和 Dependencies
1)dependencies:将所以来的 jar 直接加到项目中,子项目也会继承该依赖
2)dependencyManagement:只声明依赖,并不是先 jar 包引入,如果子项目需要用到相关依赖,需要加入子项目,不需要加入版本号,如果子项目没有指定具具体版本,会从父项目中读取 version,如果子项目中指定了版本号,就会使用子项目指定的 jar 版本,此外,父工程的打包方式应该是 pom,不是 jar,这里需要手动 packaging 来声明
4.1.2 创建子项目-订单服务
声明项目依赖和项目构建插件
<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>
4.1.3 创建子项目-商品服务
和 4.1.2 的步骤一样
4.2 完善订单服务
4.2.1 完善启动类,配置文件
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class,args);
}
}
配置文件
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=false
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml
configuration: # 配置打印 MyBatis 执行的 SQL
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true #自动驼峰转换
4.2.2 业务代码
1)实体类
@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;
}
2)Mapper
@Mapper
public interface OrderMapper {
@Select("select * from order_detail where id=#{orderId}")
OrderInfo selectOrderById(Integer orderId);
}
3)Service
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
return orderInfo;
}
}
4)Controller
@RequestMapping("/order")
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/{orderId}")
public OrderInfo getOrderInfo(@PathVariable("orderId") Integer orderId) {
return orderService.selectOrderById(orderId);
}
}
测试接口
4.3 完善商品服务
4.3.1 完善启动类,配置文件
@SpringBootApplication
public class ProductServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProductServiceApplication.class,args);
}
}
配置文件
server:
port: 9090
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/cloud_product?characterEncoding=utf8&useSSL=false
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration: # 配置打印 MyBatis 执行的 SQL
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true #自动驼峰转换
4.3.2 业务代码
1)实体类
@Data
public class ProductInfo {
private Integer id;
private String productName;
private Integer productPrice;
private Integer state;
private Date createTime;
private Date updateTime;
}
2)Mapper
@Mapper
public interface ProductMapper {
@Select("select * from product_detail where id=#{id}")
ProductInfo selectProductById(Integer id);
}
3)Service
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
public ProductInfo selectProductById(Integer id) {
ProductInfo productInfo = productMapper.selectProductById(id);
return productInfo;
}
}
4)Controller
@RequestMapping("/product")
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/{id}")
public ProductInfo getProductById(@PathVariable("id") Integer id) {
return productService.selectProductById(id);
}
}
测试接口
4.4 远程调用
4.4.1 需求
根据订单查询订单信息时,根据订单里的产品 ID 获取到产品的详情信息
4.4.2 实现
实现思路:order-service 服务向 product-service 服务发送一个 http 请求,把得到的返回结果和订单结果融合在一起,返回给调用方
实现方式:采用 Spring 提供的 RestTemplate
1)将 product-service 中的 ProductInfo 引用到 order-service 中的 OrderInfo 中
@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;
private ProductInfo productInfo;
}
2)定义 RestTemplate
@Configuration
public class BeanConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
3)修改 order-service 中的 OrderService
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
}
测试接口
5. RestTemplate
RestTemplate 是从 Spring3.0 开始支持的⼀个 HTTP 请求⼯具,它是⼀个同步的 REST API 客户端,提供了常见的 REST 请求方案的模版
REST(Representational State Transfer),表现层资源状态转移
1)资源:网路上的所有事物都可以抽象成资源,每个资源都有一个唯一的资源标识符(URI)
2)表现层:资源的表现形式,例如文本作为资源,可以用 txt 格式表现,也可以用 HTML 格式表现
3)状态转移:访问URI,也就是客户端和服务器的交互过程,客户端用到的手段,只能是HTTP协议,这个过程中可能会涉及到数据状态的变化,比如对数据的增删改查都是状态的转移
REST 是一种设计风格,指资源在网络中以某种表现形式进行状态转移,REST描述的是在网络中Client 和 Serve r的⼀种交互形式,REST本身不实用,实用的是如何设计 RESTful API(REST风格的网络接口)
RestTemplate 是Spring提供的,封装HTTP调用并强制使用 RESTful 风格,它会处理HTTP连接和关闭,只需要使用者提供资源的地址和参数即可
6. 存在问题
6.1 问题描述
1)URL 和 IP 端口号是写死的
当更换机器或者新增机器时,URL 就需要变更
2)解决思路
服务启动 / 变更时,向注册中心报道,注册中心记录应用和 IP 的关系
调用方调用时,先去注册中心获取服务方的 IP,再去服务方进行调用
6.2 注册中心
注册中心主要有三种角色:
1)服务提供者(Server):一次业务中,被其它微服务调用的服务,也就是提供接口给其它微服务
2)服务消费者(Client):一次业务中,带哦用其它微服务的服务,也就是调用其它微服务提供的接口
3)服务注册中心(Registry):用于保存 Server 的注册信息,当 Server 节点发生变更时,Registry 会同步变更,服务与注册中心使用⼀定机制通信,如果注册中心与某服务长时间无法通信就会注销该实例
服务注册:服务提供者在启动时,向 Registry 注册自身服务,并向 Registry 定期发送心跳汇报存活状态
服务发现:服务消费者从注册中心查询服务提供者的地址,并通过该地址调⽤服务提供者的接口,服务发现的⼀个重要作用就是提供给服务消费者⼀个可⽤的服务列表
6.3 CAP 理论
CAP 理论是分布式系统设计中最基础,也是最为关键的理论
1)一致性(Consistency):指的是强一致性,所有节点在同一时间具有相同的数据
对于数据库集群来说
强一致性:主库和从库无论何时,数据都是一致的,对外提供的服务都是一致的
弱一致性:随着时间的推移,主库和从库的数据最终达到了一致性
2)可用性(Availability):保证每个请求都有响应(响应的结果可能不对)
3)分区容错性(Partition Tolerance):当网络出现分区后,系统仍然能够对外提供服务
在一个分布式系统中,不可能同时满足数据一致性、服务可用性和分区容错性者三个基本需求,最多只能同时满足两个
在分布式系统中,系统间的网络不能 100% 保证,服务又必须对外保证服务,因此 Partition Tolerance 不可避免,只能在 C 和 A 中选择一个,也就是 CP 或者 AP 架构
正常情况:
网络异常:
CP 架构:为了保证分布式系统对外的数据一致性,选择不返回任何数据
AP 架构:为了保证分布式系统的可用性,节点 2 返回 V0 版本的数据(即使这个数据不正确)
6.4 常见的注册中心
1)Zookeeper
Zookeeper的官方并没有说它是⼀个注册中心,但是国内 Java 体系,大部分的集群环境都是依赖
Zookeeper 来完成注册中心的功能
2)Eureka
Eureka 是 Netflix 开发的基于 REST 的服务发现框架,主要用于服务注册、管理、负载均衡和服务故障转移
3)Nacos
Nacos 是 Spring Cloud Alibaba架构中重要的组件,除了服务注册、服务发现功能之外。Nacos还支持配置管理、流量管理、DNS、动态DNS等多种特性
CAP 理论对比
Zookeeper | Eureka | Nacos | |
CAP理论 | CP | AP | CP或AP 默认AP |