欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 美景 > 微服务架构中的精妙设计:环境和工程搭建

微服务架构中的精妙设计:环境和工程搭建

2025/4/2 6:37:05 来源:https://blog.csdn.net/LHY537200/article/details/146711406  浏览:    关键词:微服务架构中的精妙设计:环境和工程搭建

 一.前期准备

1.1开发环境安装

Oracle从JDK9开始每半年发布⼀个新版本, 新版本发布后, ⽼版本就不再进⾏维护. 但是会有⼏个⻓期维护的版本.
⽬前⻓期维护的版本有: JDK8, JDK11, JDK17, JDK21
在 JDK版本的选择上,尽量选择⻓期维护的版本.
为什么选择JDK17?
Spring Cloud 是基于 SpringBoot 进⾏开发的, SpringBoot 3.X以下的版本, Spring官⽅已不再进⾏维护(还可以继续使⽤), SpringBoot 3.X的版本, 使⽤的JDK版本基线为JDK17. 鉴于JDK21 是2023.09⽉发布的, 很多功能还没有在⽣产环境验证, 所以选择使⽤JDK17来搭建

 Mysql安装版本8.0即可

1.2分析需求

⼀个电商平台包含的内容⾮常多, 以京东为例, 仅从首页上就可以看到巨多的功能

我们该如何实现呢? 如果把这些功能全部写在⼀个服务里, 这个服务将是巨⼤的.
巨多的会员, 巨⼤的流量, 微服务架构是最好的选择.
微服务应用开发的第⼀步, 就是服务拆分. 拆分后才能进⾏"各自开发

1.3服务拆分原则

微服务到底多⼩才算"微", 这个在业界并没有明确的标准. 微服务并不是越⼩越好, 服务越⼩, 微服务架构的优点和缺点都会越来越明显.
服务越⼩, 微服务的独⽴性就会越来越⾼, 但同时, 微服务的数量也会越多, 管理这些微服务的难度也会提⾼. 所以服务拆分也要考虑场景

1.3.1单⼀职责原则

单⼀职责原则原本是⾯向对象设计中的⼀个基本原则, 它指的是⼀个类应该专注于单⼀功能. 不要存在多于⼀个导致类变更的原因
在微服务架构中, ⼀个微服务也应该只负责⼀个功能或业务领域, 每个服务应该有清晰的定义和边界, 只关注⾃⼰的特定业务领域
组织团队也是, ⼀个⼈专注做⼀件事情的效率远高于同时关注多件事情

 比如电商系统:

1.3.2服务自治

服务⾃治是指每个微服务都应该具备⾼度⾃治的能⼒, 即每个服务要能做到独⽴开发, 独⽴测试, 独⽴构建, 独⽴部署, 独⽴运⾏.(麻雀虽小,五脏俱全)
以上⾯的电商系统为例,每⼀个微服务应该有⾃⼰的存储, 配置,在进⾏开发, 构建, 部署, 运⾏和测试时,并不需要过多关注其他微服务的状态和数据

1.3.3单向依赖

微服务之间需要做到单向依赖, 严禁循环依赖, 双向依赖
  • 循环依赖: A -> B -> C ->A
  • 双向依赖: A -> B, B -> A

如果⼀些场景确实无法避免循环依赖或者双向依赖, 可以考虑使用消息队列等其他方式来实现

补充:

微服务架构并⽆标准架构, 合适的就是最好的
在架构设计的过程中, 坚持 "合适优于业界领先", 避免"过度设计"(为了设计⽽设计).

1.4设置需求

⼀个完整的电商系统是庞⼤的, 重点关注如何使⽤Spring Cloud解决微服务架构中遇到的问题
以订单列表为例:
我们简单来考虑, 这个列表提供了以下信息:
  • 订单列表
  • 商品信息
根据服务的单⼀职责原则, 我们把服务进⾏拆分为: 订单服务, 商品服务
订单服务: 提供订单ID, 获取订单详细信息
商品服务: 根据商品ID, 返回商品详细信息.

二.项目搭建

Spring Cloud 是基于SpringBoot搭建的, 所以Spring Cloud 版本与SpringBoot版本有关 

 该项⽬中使⽤的SpringBoot 版本为 3.1.6, 对应的Spring Cloud版本应该为2022.0.x, 选择任⼀就可以

2.1数据准备

 根据服务自治原则, 每个服务都应有自己独立的数据库

订单服务数据库信息:
-- 订单服务-- 建库
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);

2.2程搭建

 项目创建目前有两种:

  • 使用JavaEE的方式,使用IDEA分别创建两个项目,一个项目一个窗口(麻烦)
  • 采用父子工程的方式搭建(推荐)

1.创建父工程 

2.删除src文件(因为不需要编写代码)

3.完善pom.xml文件信息

 <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>

补充: 

pom文件DependencyManagement Dependencies区别: 

dependencies :将所依赖的jar直接加到项⽬中. ⼦项⽬也会继承该依赖 

dependencyManagement :dependencyManagement :只是声明依赖, 并不实现Jar包引⼊. 如果⼦项⽬需要⽤到相关依赖,需要显式声明. 如果⼦项⽬没有指定具体版本, 会从父项目中读取version. 如果⼦项⽬中指定了版本号,就会使⽤⼦项⽬中指定的jar版本. 此外父⼯程的打包⽅式应该是pom,不是jar, 这⾥需要⼿动使⽤ packaging 来声明.

4.创建两个子项目(order-service , product-service)

  

 

 此时父项目的pom文件就会多出modul

5.给order.pom文件和 product.pom文件添加必要的依赖

<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><resources><resource><directory>src/main/resources</directory><filtering>true</filtering><includes><include>**/**</include></includes></resource></resources>
</build>


 2.3编写order服务

 1.给order子项目创建order包 - 编写启动类

package 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);}
}

2.编写application.yml配置文件

开始编写业务代码

订单服务:

  • 根据订单ID,返回订单详情

3.先写实体类

package order.model;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;
}

4.编写mapper接口 

package order.mapper;import order.model.OrderInfo;
import org.apache.ibatis.annotations.Select;public interface OrderMapper {@Select("select * from order_detail where id = #{orderId}")OrderInfo selectOrderById(Integer orderId);//先声明方法
}

5,编写service Service主要也是从mapper接口调用订单的信息

package order.service;import order.mapper.OrderMapper;
import order.model.OrderInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;//注入这个接口public OrderInfo selectOrderById(Integer orderId){return orderMapper.selectOrderById(orderId);}
}

6.编写controller 接口

package order.controller;import order.model.OrderInfo;
import order.service.OrderService;
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;@RequestMapping("order")
@RestController
public class OrderController {@Autowiredprivate OrderService orderService;@RequestMapping("/{orderId}")public OrderInfo getOrderById(@PathVariable("orderId") Integer orderId){return orderService.selectOrderById(orderId);}
}

启动项目失败,

原因:忘记在mapper接口添加@Mapper注解

 7.项目启动成功耶耶

2.4编写product服务

给product子项目也跟上述操作类似(代码就不粘贴了)

1.编写启动类

 2.配置yml文件(注意修改端口号成9090,数据库名也要修改)

开始编写业务代码:

  • 根据商品ID,返回商品信息

 1.编写实体类信息

2.编写返回商品信息接口

 

3.编写service层

4.编写controller控制层

 5.启动项目成功


 2.5远程调用

实现两个子项目进行交互,因为订单信息里面肯定需要商品信息嘛

根据订单查询订单信息时,根据订单里产品ID,获取产品的详细信息

实现思路: order-service服务向product-service服务发送⼀个http请求, 把得到的返回结果, 和订单结果融合在⼀起, 返回给调用方.


实现方式: 采用Spring 提供的RestTemplate 

1.在将product的实体类复制到order的实体类,并在order实体类添加product类信息

2.创建config包 -  创建HTTP对象 - 定义RestTemplate

3.在servicec层使用http对象调用  

package order.service;import order.mapper.OrderMapper;
import order.model.OrderInfo;
import order.model.ProductInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;//注入这个接口@Autowiredprivate 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;}
}

4.两个项目都要启动,然后调用order路径就可以返回product的信息了

三.RestTemplate介绍

RestTemplate 是从 Spring3.0 开始⽀持的⼀个 HTTP 请求⼯具, 它是⼀个同步的 REST API 客⼾端, 提供了常见的REST请求方案的模版

 3.1什么是REST?

 REST(Representational State Transfer), 表现层资源状态转移.

可以把 REST 想象成一个大家都遵循的规则手册,让不同的软件、系统之间能够顺畅地 “交流” 和 “合作”,就像人们说同一种语言能更好地沟通一样。

这里面主要有三个概念:

  1. 资源: ⽹络上的所有事物(文字,图片,视频等等)都可以抽象为资源, 每个资源都有⼀个唯⼀的资源标识符(URI)
  2. 表现层: 资源的表现形式, ⽐如⽂本作为资源, 可以⽤txt格式表现, 也可以通过HTML, XML, JSON等格式来表现, 甚⾄以⼆进制的格式表现.
  3. 状态转移: 访问URI, 也就是客⼾端和服务器的交互过程. 客⼾端⽤到的⼿段,只能是HTTP协议. 这个过程中, 可能会涉及到数据状态的变化. ⽐如对数据的增删改查, 都是状态的转移

 REST 是⼀种设计⻛格, 指资源在⽹络中以某种表现形式进⾏状态转移

简单来说: REST描述的是在网络中Client和Server的⼀种交互形式, REST本⾝不实⽤,实⽤的是如何设计 RESTful API(REST风格的⽹络接⼝)

3.2什么是RESTful? 

REST 是⼀种设计风格, 并没有⼀个明确的标准. 满足这种设计风格的程序或接⼝我们称之为RESTful(从单词字⾯来看就是⼀个形容词). 所以RESTful API 就是满⾜REST架构风格的接⼝.

 RESTful 风格大致有以下⼏个主要特征:

  • 资源: 资源可以是⼀个图⽚, ⾳频, 视频或者JSON格式等⽹络上的⼀个实体, 除了⼀些⼆进制的资源外普通的⽂本资源更多以JSON为载体、⾯向⽤⼾的⼀组数据(通常从数据库中查询⽽得到)
  • 统⼀接⼝: 对资源的操作. ⽐如获取, 创建, 修改和删除. 这些操作正好对应HTTP协议提供的GET、POST、PUT和DELETE⽅法. 换⾔⽽知,如果使⽤RESTful⻛格的接⼝, 从接⼝上你可能只能定位其资源,但是⽆法知晓它具体进⾏了什么操作,需要具体了解其发⽣了什么操作动作要从其HTTP请求⽅法类型上进⾏判断

这些内容都是通过HTTP协议来呈现的. 所以RESTful是基于HTTP协议的,RestTemplate 是Spring提供, 封装HTTP调用, 并强制使用RESTful风格. 它会处理HTTP连接和关闭,只需要使用者提供资源的地址和参数即可。

3.3RESTful实践

RESTful⻛格的API 固然很好很规范, 但⼤多数互联⽹公司并没有按照其规则来设计, 因为REST是⼀种风格,而不是⼀种约束或规则, 过于理想的RESTful API 会付出太多的成本

 RESTful API 缺点:

  • 操作⽅式繁琐, RESTful API通常根据GET, POST, PUT, DELETE 来区分对资源的操作动作. 但是HTTP Method 并不可直接⻅到, 需要通过抓包等⼯具才能观察. 如果把动作放在URL上反⽽更加直观, 更利于团队的理解和交流.
  • ⼀些浏览器对GET, POST之外的请求⽀持不太友好, 需要额外处理.
  • 过分强调资源. ⽽实际业务需求可能⽐较复杂, 并不能单纯使⽤增删改查就能满⾜需求, 强⾏使⽤RESTful API会增加开发难度和成本

所以, 在实际开发中, 如果业务需求和RESTful API不太匹配或者很麻烦时, 也可以不⽤RESTful API. 如果使用场景和REST风格比较匹配, 就可以采用RESTful API.
总之: 无论哪种风格的API, 都是为了⽅便团队开发, 协商以及管理, 不能墨守成规. 尽信书不如无书, 尽信规范不如无规范。

3.4总结 

REST:
想象有一个超级大的 “信息超市”,里面有各种各样的 “商品”,这些 “商品” 就是资源。比如说有水果类的资源(像苹果、香蕉),电器类的资源(像电视、冰箱),REST 就是这个 “信息超市” 里大家都遵守的一套规则,有了它,不管是谁来超市(不管是哪种客户端),也不管超市是谁开的(不管是哪个服务器),大家都能按照统一的方式顺畅地交易 “商品”(交换信息)。 

RESTful:
如果说 REST 是规则,那 RESTful 就是严格遵守这个规则的 “好超市”。这个 “好超市” 里的每一个 “商品” 都有清晰准确的标签(唯一的 URI),工作人员和顾客交流时也完全按照规定的方式来(使用标准 HTTP 方法)。

RESTful 实践:

资源标识:
给每一本书都分配一个独一无二的编号,在网络里就是 URI。比如/books/1 代表编号为 1 的书,这就像给书贴上了专属标签,方便大家查找。

HTTP 方法使用:

  • GET:你在网页上输入/books/1 并发送请求,就像你在书店里跟工作人员说 “我想看看编号为 1 的书的信息”,服务器会返回这本书的详细信息,如书名、作者、价格等。
  • POST:你在网页上填写新书的信息并提交,就像你要把一本新书放到书店里卖。服务器接收到这个请求后,会创建一个新的图书资源。
  • PUT:你发现编号为 1 的书价格写错了,于是修改价格后再次提交,这就像你在书店里把书的价格标签换了。服务器会根据你提供的新信息更新这本书的资源。
  • DELETE:你觉得编号为 1 的书不再需要了,发送一个删除请求,就像你把这本书从书店的货架上拿走了。服务器会把对应的图书资源删除。

现在假设有一个在线书店,我们把它当作一个遵守 REST 规则的 “信息超市” 来实践。

通过这些方式,在线书店就能高效地管理图书信息,并且能和不同的用户(客户端)进行良好的交互,这就是 RESTful 实践.

四.项目存在问题 

  • 远程调⽤时, URL的IP和端⼝号是写死的(http://127.0.0.1:9090/product/), 如果更换IP, 需要修改代
  • 调⽤⽅如何可以不依赖服务提供⽅的IP? 如果多机部署, 如何分摊压⼒?
  • 远程调⽤时, URL非常容易写错, ⽽且复⽤性不⾼, 如何优雅的实现远程调⽤
  • 所有的服务都可以调用该接⼝, 是否有风险?

 微服务架构还面临很多问题, 接下来我们学习如何使用Spring Cloud 来解决这些问题

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词