转载

微服务架构基础之构建微服务

学习微服务架构,不但要了解微服务中的基本概念和重要组件,更重要的是实践。本文将会以一个电商中的常见业务场景为例构建微服务。在本文中,主要使用最新的SpringCloud( version:Finchley.SR1)体系进行构建。

1 准备

在开始本文之前,需要以下预备知识:

  1. 熟悉Spring和SpringBoot

  2. 了解微服务

本文会使用SpringCloud中的一些组件进行开发:

  1. Spring Cloud Netflix Eureka:注册中心

  2. Spring Cloud Netflix Zuul:API网关

  3. Spring Cloud OpenFeign: 服务调用工具

2 业务场景

为了更好的展示微服务,本文将以电商业务场景中的 创建订单 为例。现实系统中的下单非常复杂,这里会进行简化,主要是为了方便理解微服务。所以,这里假定,下单过程中的两个主要操作:

  • 根据下单用户的id,查询当前用户的信息

  • 根据下单的商品id,查询当前商品的信息

最终,根据这个业务场景,设计的微服务架构图如下:

微服务架构基础之构建微服务

按照业务场景,将不同的功能垂直划分成三个服务:

  1. UserService

  2. OrderService

  3. ProductService

其中,OrderService会调用UserService和ProductService的相关服务,在调用过程中,并不是直接调用,而是通过Eureka去调用它们。外部调用不会直接调用具体的服务,全部都是通过Zuul网关进行调用,此处,Zuul也是通过Eureka去调用具体的服务。

3 构建项目

根据架构图,构建项目骨架。创建一个Maven项目,并且创建五个子模块,pom.xml:

 1<?xml version="1.0" encoding="UTF-8"?>
 2<project xmlns="http://maven.apache.org/POM/4.0.0"
 3         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0      http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5    <modelVersion>4.0.0</modelVersion>
 6    <parent>
 7        <groupId>org.springframework.boot</groupId>
 8        <artifactId>spring-boot-starter-parent</artifactId>
 9        <version>2.0.1.RELEASE</version>
10    </parent>
11    <groupId>com.no.one</groupId>
12    <artifactId>microservice-base</artifactId>
13    <packaging>pom</packaging>
14    <version>1.0-SNAPSHOT</version>
15
16    <properties>
17        <java.version>1.8</java.version>
18    </properties>
19
20    <dependencyManagement>
21        <dependencies>
22            <dependency>
23                <groupId>org.springframework.cloud</groupId>
24                <artifactId>spring-cloud-dependencies</artifactId>
25                <version>Finchley.SR1</version>
26                <type>pom</type>
27                <scope>import</scope>
28            </dependency>
29        </dependencies>
30    </dependencyManagement>
31
32    <modules>
33        <module>discovery-server</module>
34        <module>api-gateway-server</module>
35        <module>order-service</module>
36        <module>product-service</module>
37        <module>user-service</module>
38    </modules>
39
40    <build>
41        <plugins>
42            <plugin>
43                <groupId>org.springframework.boot</groupId>
44                <artifactId>spring-boot-maven-plugin</artifactId>
45            </plugin>
46        </plugins>
47    </build>
48
49</project>

在这个pom文件中,我们定义了所使用的SpringCloud以及SpringBoot相关库的版本。同时,在各个模块中,我们将使用 .properties 文件作为项目配置文件,而不使用 .yml 文件。 yml 文件虽然层次分明,但是基于缩进的语法,容易出问题。关于这个文件格式的选择,要考虑到个人习惯以及团队内的约定。

3.1 注册中心:Eureka

创建Eureka注册中心,首先要引入依赖,在配置文件 microservice-base/discovery-server/pom.xml

1<dependencies>
2    <dependency>
3        <groupId>org.springframework.cloud</groupId>
4        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
5    </dependency>
6</dependencies>

然后创建主类, com.no.one.discovery.DiscoveryApplication

1@SpringBootApplication
2@EnableEurekaServer
3public class DiscoveryApplication {
4    public static void main(String[] args) {
5        SpringApplication.run(DiscoveryApplication.class, args);
6    }
7}

这是一个SpringBoot应用,关键就在于使用 @EnableEurekaServer 注解。然后,增加相关的配置:

microservice-base/discovery-server/src/main/resources/bootstrap.properties:

1spring.application.name=discovery-server

microservice-base/discovery-server/src/main/resources/application.properties:

1server.port=8761
2eureka.instance.hostname=localhost
3
4eureka.client.registerWithEureka=false
5eureka.client.fetchRegistry=false
6eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

这个配置文件中,配置了Eureka服务器的参数。

3.2 API网关: Zuul

引入Zuul的依赖,在配置文件 microservice-base/api-gateway-server/pom.xml :

 1<dependencies>
 2    <dependency>
 3         <groupId>org.springframework.cloud</groupId>
 4        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 5    </dependency>
 6    <dependency>
 7         <groupId>org.springframework.cloud</groupId>
 8         <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
 9    </dependency>
10</dependencies>

注意,在Zuul网关中,调用上游服务时,是通过调用Eureka进行的,所以,此处引入eureka-client的依赖。

创建主类 com.no.one.gateway.GatewayApplication.java

1@SpringBootApplication
2@EnableDiscoveryClient
3@EnableZuulProxy
4public class GatewayApplication {
5    public static void main(String[] args) {
6        SpringApplication.run(GatewayApplication.class, args);
7    }
8}

这也是一个SpringBoot应用,关键就在于使用 @EnableZuulProxy 注解,该注解会启用反向代理,后面会看到相关的路由配置。同时,通过 @EnableDiscoveryClient 注解,集成对于Eureka的支持,后续,在配置路由时可以直接使用服务的名字进行。

然后,增加相关配置 microservice-base/api-gateway-server/src/main/resources/bootstrap.properties :

1spring.application.name=api-gateway-server

microservice-base/api-gateway-server/src/main/resources/application.properties:

 1server.port=9002
 2
 3eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
 4
 5zuul.prefix=/api
 6zuul.ignoredServices='*'
 7
 8zuul.routes.user-service.path=/user-service/**
 9zuul.routes.user-service.serviceId=user-service
10
11zuul.routes.product-service.path=/product-service/**
12zuul.routes.product-service.serviceId=product-service
13
14zuul.routes.order-service.path=/order-service/**
15zuul.routes.order-service.serviceId=order-service

在此处,除了Zuul网关的配置,还配置好相关的路由。

3.3 业务服务

完成基础组件,开始构建具体的业务服务。三个业务组件都是SpringBoot应用,构建方法类似。同时,为了简单起见,支持使用内存数据库,数据库的访问直接使用SpingDataJPA。以构建OrderService为例,首先,创建 microservice-base/order-service/pom.xml 文件,并引入依赖:

 1<dependencies>
 2 <!-- Spring Boot -->
 3    <dependency>
 4        <groupId>org.springframework.boot</groupId>
 5         <artifactId>spring-boot-starter-data-jpa</artifactId>
 6    </dependency>
 7    <dependency>
 8         <groupId>org.springframework.boot</groupId>
 9         <artifactId>spring-boot-starter-web</artifactId>
10    </dependency>
11    <!-- Spring Cloud -->
12    <dependency>
13         <groupId>org.springframework.cloud</groupId>
14         <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
15    </dependency>
16     <dependency>
17         <groupId>org.springframework.cloud</groupId>
18         <artifactId>spring-cloud-starter-openfeign</artifactId>
19     </dependency>
20    <!-- Third parties -->
21    <dependency>
22        <groupId>org.projectlombok</groupId>
23        <artifactId>lombok</artifactId>
24    </dependency>
25    <dependency>
26        <groupId>org.hsqldb</groupId>
27        <artifactId>hsqldb</artifactId>
28    </dependency>
29</dependencies>

主要的配置 microservice-base/order-service/src/main/resources/bootstrap.properties :

1spring.application.name=order-service

以上名字属性,作为服务名注册到Eureka中。

microservice-base/order-service/src/main/resources/application.properties :

1server.port = 9200
2eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

相关的主要Java类:

实体类 microservice-base/order-service/src/main/java/com/no/one/order/model/Order.java :

 1@Data
 2@Entity
 3@Table(name = "ms_order")
 4public class Order {
 5    @Id
 6    @GeneratedValue(strategy = GenerationType.IDENTITY)
 7    private Long id;
 8
 9    @Column(name = "product_name")
10    private String productName;
11
12    @Column(name = "user_name")
13    private String userName;
14
15    @Column(name = "price")
16    private BigDecimal price;
17
18    @Column(name ="created_time")
19    private Date createdTime;
20}

数据访问 microservice-base/order-service/src/main/java/com/no/one/order/repo/OrderRepo.java :

1public interface OrderRepo extends JpaRepository<Order, Long> {
2}

业务接口 microservice-base/order-service/src/main/java/com/no/one/order/service/OrderService.java

1public interface OrderService {
2    Order createOrder(Long userId, Long productId);
3    List<Order> listOrder();
4}

在实现业务接口之前,要实现对于ProductService和UserService的服务调用。此处,直接使用 Open Feign ,该库可以极大简化对于远程服务的调用,并且,使用SpringMVC里的各种常用注解。

调用ProductService, microservice-base/order-service/src/main/java/com/no/one/order/client/ProductClient.java :

1@FeignClient("product-service")
2public interface ProductClient {
3    @RequestMapping(method = RequestMethod.GET, value = "/products/{id}")
4    ProductDto getProduct(@PathVariable("id") Long id);
5}

最后,实现 OrderService 中的业务接口 microservice-base/order-service/src/main/java/com/no/one/order/service/impl/OrderServiceImpl.java

 1@Service
 2@RequiredArgsConstructor
 3public class OrderServiceImpl implements OrderService {
 4
 5    private final OrderRepo orderRepo;
 6    private final UserClient userClient;
 7    private final ProductClient productClient;
 8
 9    @Override
10    public Order createOrder(Long userId, Long productId) {
11        Order order = new Order();
12
13        UserDto user = userClient.getUser(userId);
14        order.setUserName(user.getName());
15
16        ProductDto product = productClient.getProduct(productId);
17        order.setProductName(product.getName());
18        order.setPrice(product.getPrice());
19
20        order.setCreatedTime(new Date());
21        return orderRepo.save(order);
22    }
23
24    @Override
25    public List<Order> listOrder() {
26        return orderRepo.findAll();
27    }
28}

createOrder 方法中,会调用两个远程的服务,然后,创建订单实体对象,最终数据保存到数据库中。

创建RestAPI, microservice-base/order-service/src/main/java/com/no/one/order/controller/OrderController.java

 1@RequiredArgsConstructor
 2@RestController
 3@RequestMapping("/orders")
 4public class OrderController {
 5
 6    private final OrderService orderService;
 7
 8    @PostMapping
 9    public Order createOrder(@RequestBody CreateOrderRequest request){
10        return orderService.createOrder(request.getUserId(), request.getProductId());
11    }
12
13    @GetMapping
14    public List<Order> listOrder(){
15        return orderService.listOrder();
16    }
17
18    @Data
19    public static class CreateOrderRequest {
20        private Long userId;
21        private Long productId;
22    }
23}

ProductService和OrderService的构建类似,不过,我们会给这两个服务增加一些数据,他们会在项目启动时初始化到内存数据库中:

microservice-base/user-service/src/main/resources/data.sql

1INSERT INTO ms_user VALUES (1, 'Zeus');
2INSERT INTO ms_user VALUES (2, 'Hera');
3INSERT INTO ms_user VALUES (3, 'Hades');

microservice-base/product-service/src/main/resources/data.sql

1INSERT INTO ms_product VALUES (1, 'iPhone4', 99);
2INSERT INTO ms_product VALUES (2, 'iPhone6',999.99);
3INSERT INTO ms_product VALUES (3, 'iPhone8', 9999.98);

此时,我们可以借助一些RestAPI测试工具,测试创建订单的接口:

POST http://localhost:9002/api/order-service/orders

HTTP请求的请求体:

1{
2    "userId":1,
3    "productId":1
4}

成功后,返回数据:

1{
2    "id": 1,
3    "productName": "iPhone4",
4    "userName": "Zeus",
5    "price": 99,
6    "createdTime": "2018-08-27T12:17:02.406+0000"
7}

4 小结

本文结合一个电商中常见的创建订单这个业务场景,使用微服务架构进行构建。结合之前的微服务架构基础系列文章,理论联系实践,可以更好的理解微服务架构。当然,这是一个简单的例子,现实中的开发往往要更加复杂。

5 微服务架构基础系列

使用ELK构建微服务的日志平台

微服务架构基础之注册中心

微服务架构基础之API网关

更多精彩内容,关注公众号 SeniorEngineer :

微服务架构基础之构建微服务

原文  https://mp.weixin.qq.com/s/_e-2VzWau6YGPemQ5mTSow
正文到此结束
Loading...