之前整理过一些微服务的相关知识,这里打算通过一个小demo逐渐了解微服务的搭建过程。
环境
- IDE:IntelliJ 社区版2018.1
- JDK: Open jdk 11
- SpringBoot: 2.2.6.RELEASE
业务逻辑
模仿一个商城,在自己的订单(order)中查询商品(product)
创建maven聚合工程
父工程
步骤:File->New->Project->maven,定义好groupId 和artifactId 然后下一步。创建成功后删除src,并导入相应的依赖.
-
工程目录
-
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>cn.research</groupId>
<artifactId>spring_cloud_demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>product_service</module>
<module>order_service</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>11</java.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-logging -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId> <!-- This one is super important for web controller -->
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR10</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
子工程(product 服务)
此服务提供了根据商品id查询商品的API
这里只展示代码结构和Controller层的代码,其他部分的代码,可以免分下载。
步骤:spring_cloud_demo工程名上右击New->Module->maven,输入artifact_id:product_service
- 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">
<parent>
<artifactId>spring_cloud_demo</artifactId>
<groupId>cn.research</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>product_service</artifactId>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
</project>
- 在数据库cloud创建product表
CREATE TABLE `product` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
`price` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
3.搭建各层的结构,项目结构下
其中controller中向外暴露了一个接口:
package com.research.product.controller;
import com.research.product.entity.Product;
import com.research.product.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* @author :TS六道轮回
* @date :Created in 2021/3/6 22:47
* @description:${description}
*/
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
ProductService productService;
@GetMapping(value = "/{id}")
public Product findById(@PathVariable Long id){
return productService.findById(id);
}
}
- 配置文件
server:
port: 9001 #端口
spring:
favicon:
enabled: false
application:
name: service-product #服务名称
datasource:
username: root
password: abcde
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/cloud?useUnicode=true&characterEncoding=UTF-8
jpa:
database: MySQL
show-sql: true
open-in-view: true
- 程序运行结果
子工程(order 服务)
此服务通过调用product 服务提供的接口来获取商品。
同product 服务一样,这里也只展示代码结构和Controller层以及与product服务不一致的地方,其他部分的代码,可以免分下载。
步骤:spring_cloud_demo工程名上右击New->Module->maven,输入artifact_id:order_service
- 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">
<parent>
<artifactId>spring_cloud_demo</artifactId>
<groupId>cn.research</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order_service</artifactId>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
</project>
- 搭建各层的结构,项目结构下
为了模拟简单的功能,order服务只访问product服务,结构上主要有如下区别
a. 没有与数据库进行交互,因此没有Dao层和Entity包
b. 对product服务的访问放到Controller层中完成,因此这里没有Service层,真正开发过程中应该是在Service层中完成。
c. 对product的访问使用了RestTemplate, 在config中将其加入到容器中,因此多了config包。
d. 对返回值处理时使用了product dto来接受的,因此多了dto包。
config中使用@Bean创建RestTemplate
package com.research.order.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author :TS六道轮回
* @date :Created in 2021/3/9 22:02
*/
@Configuration
public class Config {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
controller中使用RestTemplate调用product服务,并且向外暴露接口
package com.research.order.controller;
import com.research.order.dto.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* @author :TS六道轮回
* @date :Created in 2021/3/9 21:48
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
RestTemplate restTemplate;
@GetMapping(value = "/buy/{id}")
public Product findById(@PathVariable Long id) {
Product product = null;
//真正的开发过程这部分代码应该放到service层里
product = restTemplate.getForObject("http://localhost:9001/product/"+id,Product.class);
return product;
}
}
- 配置文件
没有与数据库进行交互,因此删掉数据库相关的配置
server:
port: 9002 #端口
spring:
favicon:
enabled: false
application:
name: service-order #服务名称
- 程序运行结果
分析代码问题
通过上面的例子,虽然服务之间的通信没有问题,但随着规规模不断的扩大,相应的问题也随之出现,通过下图分析
- 服务调用者将服务提供者的接口路径硬编码到自己的代码中,如果服务提供者修改接口路径,服务调用者必定要修改,无形中增加了调用者开发难度。
- 当前事例中只有一个服务提供者,随着业务的扩展服务提供者的数量会越来越多,如果所有的接口路径都硬编码到服务调用者中,一旦发生变动,服务调用者的修改工作量无法估量
- 为了提供系统的可靠性,一个服务提供者必定会构建多个节点,服务调用者又如何去选择哪一个节点,因此负载均衡无法保证。
- 服务调用者需要知道每个服务的地址,任务太重了,如果中间有一个管理者就好了,服务调用者只需要将请求发给这个中间人就可以了,因此无法实现网关的功能,
- 当服务调用者需要的服务来自各个服务提供者的协作完成时,这种情况日志又如何记录,因此链路追踪无法保证。
- 当服务提供者的数量越来越多时,相应的配置文件也随之增多,一旦修改某个配置文件,将造成其他服务的变动,因此无法对配置进行统一管理。
可见,自己手动搭一套微服务框架,需要我们自己解决很多的问题,并且解决起来都不是那么简单的一件事,好在SpringCloud为我们提供了解决方案,在下一篇博客中,介绍如何使用SpringCloud来搭建微服务。