环境和工程搭建

ops/2024/12/17 8:37:48/

1.案例介绍

1.1 需求

实现⼀个电商平台

该如何实现呢? 如果把这些功能全部写在⼀个服务⾥, 这个服务将是巨⼤的.

巨多的会员, 巨⼤的流量, 微服务架构是最好的选择.

微服务应⽤开发的第⼀步, 就是服务拆分. 拆分后才能进⾏"各⾃开发"

1.2 服务拆分

拆分原则

微服务到底多⼩才算"微", 这个在业界并没有明确的标准. 微服务并不是越⼩越好, 服务越⼩, 微服务架构 的优点和缺点都会越来越明显.

服务越⼩, 微服务的独⽴性就会越来越⾼, 但同时, 微服务的数量也会越多, 管理这些微服务的难度也会提⾼. 所以服务拆分也要考虑场景.

①单⼀职责原则

单⼀职责原则原本是⾯向对象设计中的⼀个基本原则, 它指的是⼀个类应该专注于单⼀功能. 不要存在多余一个导致类变更的原因

微服务架构中, ⼀个微服务也应该只负责⼀个功能或业务领域, 每个服务应该有清晰的定义和边界, 只 关注⾃⼰的特定业务领域.

②服务⾃治

服务⾃治是指每个微服务都应该具备⾼度⾃治的能⼒, 即每个服务要能做到独⽴开发, 独⽴测试, 独⽴构 建, 独⽴部署, 独⽴运⾏.

以上⾯的电商系统为例,每⼀个微服务应该有⾃⼰的存储, 配置,在进⾏开发, 构建, 部署, 运⾏和测试时,并不需要过多关注其他微服务的状态和数据

③单项依赖

微服务之间需要做到单向依赖, 严禁循环依赖, 双向依赖

循环依赖: A -> B -> C ->A

双向依赖: A -> B, B->A

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

微服务架构并⽆标准架构, 合适的就是最好的, 不然架构师⼤会也不会各个系统架构百花⻬放 了

在架构设计的过程中, 坚持 "合适优于业界领先", 避免"过度设计"(为了设计⽽设计)

 

服务拆分示例(电商系统为例)

根据服务的单⼀职责原则, 我们把服务进⾏拆分为:

订单服务, 商品服务 订单服务: 提供订单ID, 获取订单详细信息

商品服务: 根据商品ID, 返回商品详细信息.

2. 数据准备

根据服务⾃治原则, 每个服务都应有⾃⼰独⽴的数据库

-- 订单服务

-- 建库

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

3. 工程搭建

3.1 构建父子工程

3.1.1 构建父工程

创建⼀个空的Maven项⽬, 删除所有代码, 只保留pom.xml

完善pom文件

使⽤properties来进⾏版本号的统⼀管理, 使⽤dependencyManagement来管理依赖, 声明⽗⼯程的打包⽅式为pom

java"><?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>SpringCloud-demo</artifactId><version>1.0-SNAPSHOT</version><modules><module>order-service</module><module>product-service</module></modules><packaging>pom</packaging><!--本身自带的这些要删除的--><!--    <properties>--><!--        <maven.compiler.source>17</maven.compiler.source>--><!--        <maven.compiler.target>17</maven.compiler.target>--><!--        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>--><!--    </properties>--><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.6</version><!--        基于SPringboot3.16版本创建的--><relativePath/> <!-- lookup parent from repository --></parent><properties><!--        声明了Maven,JDK,Mybaits,MySQL的版本--><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><!--        将所依赖的jar直接加到项⽬中. ⼦项⽬也会继承该依赖.--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies><dependencyManagement><!--      只是声明依赖, 并不实现Jar包引⼊. 如果⼦项⽬需要⽤到相关依赖,需要显式声明.如果⼦项⽬没有指定具体版本, 会从⽗项⽬中读取version. 如果⼦项⽬中指定了版本号,就会使⽤⼦项⽬中指定的jar版本.此外⽗⼯程的打包⽅式应该是pom,不是jar, 这⾥需要⼿动使⽤ packaging 来声明  --><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><!--   spring-cloud的版本要和SPringboot的版本对应上来,SpringCloud2022.0.x和SpringBoot3.0.x,3.1.x对应--><type>pom</type><scope>import</scope></dependency><!--            其余依赖的版本也要与SPringboot版本对应起来--><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>

dependencies :

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

dependencyManagement :

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

3.1.2 创建子项目-订单服务

完善pom文件

 

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


3.1.3 创建子项目-商品服务

完善pom文件 
java"><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>

 

3.2 完善订单服务

3.2.1 完善启动类, 配置⽂件

 

server:port: 8080
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=falseusername: rootpassword: 808250driver-class-name: com.mysql.cj.jdbc.Driver
# 设置 Mybatis 的 xml 保存路径
mybatis:mapper-locations: classpath:mapper/*Mapper.xmlconfiguration: # 配置打印 MyBatis 执行的 SQLlog-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true  #自动驼峰转换

3.2.2 业务代码

实体类

java">@Data
public class OrderInfo {
//            `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(),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;
}

 

Controller

java">@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate OrderService orderService;@RequestMapping("/{orderId}")public OrderInfo getOrderById(@PathVariable("orderId") Integer orderId){return orderService.selectOrderById(orderId);}
}

Service

java">@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;public OrderInfo selectOrderById(Integer orderId){return orderMapper.selectOrderById(orderId);}}

Mapper 

java">@Mapper
public interface OrderMapper {@Select("select * from order_detail where id=#{orderId}")OrderInfo selectOrderById(Integer orderId);
}

3.2.3 测试

127.0.0.1:8080/order/1

3.3 完善商品服务 

3.3.1 完善启动类, 配置⽂件 

3.3.2 业务代码

实体类

java">@Data
public class ProductInfo {
//    `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(),private Integer Id;private String productName;private Integer productPrice;private Integer state;private Date createTime;private Date updateTime;
}
server:port: 9090
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/cloud_product?characterEncoding=utf8&useSSL=falseusername: rootpassword: 808250driver-class-name: com.mysql.cj.jdbc.Driver
# 设置 Mybatis 的 xml 保存路径
mybatis:mapper-locations: classpath:mapper/*Mapper.xmlconfiguration: # 配置打印 MyBatis 执行的 SQLlog-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true  #自动驼峰转换

Controller

java">@RestController
@RequestMapping("/product")
public class ProductController {@Autowiredprivate ProductService productService;@RequestMapping("/{productId}")public ProductInfo getProductById(@PathVariable("productId") Integer productId){return productService.selectProductById(productId);}
}

Service

java">@Service
public class ProductService {@Autowiredprivate  ProductMapper productMapper;public ProductInfo selectProductById(Integer id){return productMapper.selectProductById(id);}
}

Mapper 

java">
@Mapper
public interface ProductMapper {@Select("select * from product_detail where id= #{id}")ProductInfo selectProductById(Integer id);
}

3.3.3 测试

127.0.0.1:9090/product/1001 

 

3.4 远程调⽤

3.4.1 需求

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

3.4.2 实现

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

实现⽅式: 采⽤Spring 提供的RestTemplate

定义RestTemplate

java">@Configuration
public class BeanConfig {@Beanpublic RestTemplate restTemplate(){return new RestTemplate();}
}

修改order-service中的 OrderService

 

java">@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate RestTemplate restTemplate;//    public OrderInfo selectOrderById(Integer orderId){
//        return orderMapper.selectOrderById(orderId);
//    }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);//返回的类型是porderInfo.setProductInfo(productInfo);//把商品信息放到orderinforeturn orderInfo;}
}

3.4.3 测试

4.  RestTemplate

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

什么是REST?

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

这⾥⾯主要有三个概念:

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

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

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

什么是RESTful?

REST 是⼀种设计⻛格, 并没有⼀个明确的标准.

满⾜这种设计⻛格的程序或接⼝我们称之为RESTful(从单词字⾯来看就是⼀个形容词). 所以RESTful API 就是满⾜REST架构⻛格的接⼝.

RESTful ⻛格⼤致有以下⼏个主要特征:

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

⽐如同⼀个的URL:

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

DELETE /blog/{blogId}:删除博客

这些内容都是通过HTTP协议来呈现的. 所以RESTful是基于HTTP协议的.

RestTemplate 是Spring提供, 封装HTTP调⽤, 并强制使⽤RESTful⻛格. 它会处理HTTP连接和关闭, 只需要使⽤者提供资源的地址和参数即可

 

RESTful实践

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

缺点:

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

所以, 在实际开发中, 如果业务需求和RESTful API不太匹配或者很⿇烦时, 也可以不⽤RESTful API. 如果使⽤场景和REST⻛格⽐较匹配, 就可以采⽤RESTful API.

总之: ⽆论哪种⻛格的API, 都是为了⽅便团队开发, 协商以及管理, 不能墨守成规. 尽信书不如⽆书, 尽信 规范不如⽆规范.

5. 项⽬存在问题

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

 


http://www.ppmy.cn/ops/142602.html

相关文章

第三十一章 Spring之假如让你来写事务——融入IOC容器篇

Spring源码阅读目录 第一部分——IOC篇 第一章 Spring之最熟悉的陌生人——IOC 第二章 Spring之假如让你来写IOC容器——加载资源篇 第三章 Spring之假如让你来写IOC容器——解析配置文件篇 第四章 Spring之假如让你来写IOC容器——XML配置文件篇 第五章 Spring之假如让你来写…

Python解释器和PyCharm详解

目录 1.什么是Python解释器? Python解释器的类型和特性 Python解释器的优势 2.什么工具可以写Python文件? 3.为什么Python解释器和PyCharm不在同一个网站? 1.什么是Python解释器? Python解释器‌是Python程序运行的核心&#xff0c;它的主要作用是将Python代码转换为…

人工智能对我们的生活影响有多大?

人工智能的深刻影响 引言 你好&#xff0c;欢迎来到这个关于人工智能&#xff08;AI&#xff09;的探索之旅&#xff01;在当今社会&#xff0c;人工智能已经渗透到我们生活的每一个角落&#xff0c;从家庭和工作场所&#xff0c;到教育和社会经济领域&#xff0c;都留下了深…

《知识拓展 · 统一建模语言UML》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

Redis篇-20--运维篇2-哨兵机制(主从节点监听,故障转移,高可用,配置案例)

1、概述 Redis哨兵机制是一种高可用的解决方案&#xff0c;它通过一组哨兵进程来监控Redis主从节点的运行状态。当主节点出现故障时&#xff0c;哨兵会自动将其中一个从节点提升为新的主节点&#xff0c;并通知其他从节点和客户端更新配置&#xff0c;从而实现自动故障转移。 …

基于WEB开发的高校学籍管理系统设计与实现

标题:基于WEB开发的高校学籍管理系统设计与实现 内容:1.摘要 随着高校规模的不断扩大和学生数量的增加&#xff0c;学籍管理工作变得越来越复杂和繁重。传统的手工管理方式已经无法满足高校学籍管理的需求&#xff0c;因此需要设计和实现一个基于 WEB 的高校学籍管理系统&…

xshell连接虚拟机,更换网络模式:NAT->桥接模式

NAT模式&#xff1a;虚拟机通过宿主机的网络访问外网。优点在于不需要手动配置IP地址和子网掩码&#xff0c;只要宿主机能够访问网络&#xff0c;虚拟机也能够访问。对外部网络而言&#xff0c;它看到的是宿主机的IP地址&#xff0c;而不是虚拟机的IP。但是&#xff0c;宿主机可…

JS哪些操作会造成内存泄露?

在 JavaScript 中&#xff0c;内存泄露是指程序不再使用的内存没有被释放&#xff0c;从而导致内存的持续增长&#xff0c;最终可能导致性能下降或应用崩溃。以下是一些常见的可能导致内存泄露的操作和情况&#xff1a; 1. 全局变量 如果不小心创建了全局变量&#xff0c;可能…