从架构发展的⻆度来描述技术的发展过程,根据不同阶段所⾯临的问题来推动架构的演变,从而更好地理解微服务的本质以及它所带来的的好处。
1.1 从单体架构到微服务架构的演进
1.1.1 单体架构
通过我们之前的学习,我们开发⼀个⼀个Web项⽬,可以使⽤Spring、SpringMVC、Mybatis等技术。
整个系统的架构⾮常简单,使⽤Spring+SpringMVC+Mybatis构建⼀个基础⼯程、MySQL数据库作为持久化存储,在这个⼯程中创建不同的Service实现项⽬中不同的业务场景,如线路、线路分类、⽤户等。最后把项⽬构建成⼀个war包部署在Tomcat容器上即可使⽤,这时我们之前经常采⽤的架构。
通常来说,如果⼀个war包或者jar包⾥⾯包含⼀个应⽤的所有功能,则我们称这种架构为单体架构。
很多传统互联⽹公司或者创业型公司早期基本都会采⽤这样的架构,因为这样的架构⾜够简单,能够快速开发和上线。⽽且对于项⽬初期⽤户量不⼤的情况,这样的架构⾜以⽀撑业务的正常运⾏。
1.1.2 集群和垂直化
以商城系统为例。随着业务的发展,产品被越来越多的⼈使⽤,那么对于整个技术架构来说,可能会⾯临以下挑战:
-
⽤户量越来越⼤,⽹络访问量不断增⼤,导致后端服务器的负载越来越⾼。
-
⽤户量⼤了,产品需要满⾜不同⽤户的需求来留住⽤户,使得业务场景越来越多并且越来越复杂。
-
当服务器的负载越来越⾼的时候,如果不进⾏任何处理,⽤户在⽹站上操作的响应会越来越慢,甚⾄出现⽆法访问的情况,对于⾮常注重⽤户体验的互联⽹产品来说,这是⽆法容忍的。
-
业务的场景越多越复杂,意味着war包中的代码量会持续上升,并且各个业务代码之间的耦合度也会越来越⾼,后期的代码维护和版本发布涉及的测试和上线,也会很困那。举个最简单的例⼦,当你需要在库存模块⾥⾯添加⼀个⽅法时,带来的影响是需要把整个系统重新测试和部署,⽽当⼀个war包有⼏GB的⼤⼩时,部署的过程也是相当痛苦的。
因此,我们可以从两个⽅⾯进⾏优化:
-
通过横向增加服务器,把单台机器变成多台机器的集群。
-
按照业务的垂直领域进⾏拆分,减少业务的耦合度,以及降低单个war包带来的伸缩性困难问题。
如图所示,我们把商城系统按照业务维度进⾏了垂直拆分:⽤户⼦系统、库存⼦系统、商品⼦系统,每个⼦系统由不同的业务团队负责维护并且独⽴部署。同时,我们针对Tomcat服务器进⾏了集群部署,也就是把多台Tomcat服务器通过⽹络进⾏连接组合,形成⼀个整体对外提供服务。这样做的好处是能够在改变应⽤本身的前提下,通过增加服务器来进⾏⽔平扩容从⽽提升整个系统的吞吐量。
需要注意的是,图中针对数据库进⾏了垂直分库,主要是考虑到Tomcat服务器能够承载的流量⼤了之后,如果流量都传导到数据库上,会给数据库造成⽐较⼤的压⼒。
总体来说,数据库层⾯的拆分思想和业务系统的拆分思想是⼀样的,都是采⽤分⽽治之的思想。
1.1.3 SOA架构
为了让⼤家更好地理解SOA,我们来看两个场景:
-
假设⼀个⽤户执⾏下单操作,系统的处理逻辑是先去库存⼦系统检杳商品的库存,只有在库存⾜够的情况下才会提交订单,那么这个检查库存的逻辑是放在订单⼦系统中还是库存⼦系统呢?在整个系统中,⼀定会存在⾮常多类似的共享业务的场景,这些业务场景的逻辑肯定会被重复创建,从⽽产⽣⾮常多冗余的业务代码,这些冗余代码的维护成本会随着时间的推移越来越⾼,能不能够把这些共享业务逻辑抽离出来形成可重⽤的服务呢?
-
在⼀个集团公司下有很多⼦公司,每个⼦公司都有⾃⼰的业务模式和信息沉淀,各个⼦公司之间不进⾏交互和共享。这个时候每个⼦公司虽然能够创造⼀定的价值,但是由于各个⼦公司之间信息不是互联互通的,彼此之间形成了信息孤岛,使得价值⽆法最⼤化。
基于这些问题,就引⼊了SOA (Service-Oriented Architecture ),也就是⾯向服务的架构,从语义上说,它和⾯向过程、⾯向对象、⾯向组件的思想是⼀样的,都是⼀种软件组建及开发的⽅式。核⼼⽬标是把⼀些通⽤的、会被多个上层服务调⽤的共享业务提取成独⽴的基础服务。这些被提取出来的共享服务相对来说⽐较独⽴,并且可以重⽤。所以在SOA中,服务是最核⼼的抽象⼿段,业务被划分为⼀些粗粒度的业务服务和业务流程。
如图所示,提取出了⽤户服务、库存服务、商品服务等多个共享服务。在SOA中,会采⽤ESB(企业服务总线)来作为系统和服务之间的通信桥梁,ESB本身还提供服务地址的管理、不同系统之间的协议转化和数据格式转化等。调⽤端不需要关⼼⽬标服务的位置,从⽽使得服务之间的交互是动态的,这样做的好处是实现了服务的调⽤者和服务的提供者之间的⾼度解耦。总的来说,SOA主要解决的问题是:
- 信息孤岛。
- 共享业务的重⽤。
1.1.4 微服务架构
业务系统实施服务化改造之后,原本共享的业务被拆分形成可复⽤的服务,可以在最⼤程度上避免共享业务的重复建设、资源连接瓶颈瓶颈等问题。那么被拆分出来的服务是否也需要以业务功能为维度来进⾏拆分和独⽴部署,以降低业务的耦合及提升容错性呢?
微服务就是这样⼀种解决⽅案,从名字上来看,⾯向服务(SOA)和微服务本质上都是服务化思想的⼀种体现。如果SOA是⾯向服务开发的思想的雏形,那么微服务就是针对可重⽤业务服务的更进⼀步优化,我们可以把SOA看成微服务的超集,⼀但服务规模扩⼤就意味着服务的构建、发布、运维的复杂度也会成倍增加,所以实施微服务的前提是软件交付链路及基础设施的成熟化。因此微服务并不是⼀个新的概念,他本质上是服务化思想的最佳实践⽅向。由于SOA和微服务两者的关注点不⼀样,造成了这两者有⾮常⼤的区别:
SOA关注的是服务的重⽤性及解决信息孤岛问题。
微服务关注的是解耦,虽然解耦和可重⽤性从特定的⻆度来看是⼀样的,但本质上是有区别的,解耦是降低业务之间的耦合度,⽽重⽤性关注的是服务的复⽤。
微服务会更多地关注在DevOps的持续交付上,因为服务粒度细化之后使得开发运维变得更加重要,因此微服务与容器化技术的结合更加紧密。
如图所示,将每个具体的业务服务构成可独⽴运⾏的微服务,每个微服务只关注某个特定的功能,服务之间采⽤轻量级通信机制REST API进⾏通信。细⼼的读者会发现SOA中的服务和微服务架构中的服务粒度是⼀样的,不是说SOA是微服务的超集吗?其实我们可以把⽤户服务拆分的更细,⽐如⽤户注册服务、⽤户鉴权服务等。实际上,微服务到底要拆分到多⼤的粒度没有统⼀的标准,更多的时候是需要在粒度和团队之间找平衡的,微服务的粒度越⼩,服务独⽴性带来的好处就越多,但是管理⼤量的微服务也会越复杂。
1.2 微服务简介
⽐如我们⽹上购物,⾸先应该去购物⽹站搜索商品,这个搜索功能就可以开发成⼀个微服务。我们也可以看到相关商品的推荐,这些推荐项也可以是⼀个微服务。后⾯⽐如加⼊购物⻋、下订单、⽀付等功能都可以开发成⼀个⼀个的独⽴运⾏的微服务。
微服务应具有如下特点:
-
微服务是⼀种架构⻛格。
-
微服务把⼀个应⽤拆分为⼀组⼩型服务。
-
微服务每个服务运⾏在⾃⼰的进程内,也就是可独⽴部署和升级。
-
微服务的服务之间使⽤轻量级HTTP交互,⼀般使⽤Json交换数据。
-
服务围绕业务功能拆分。
-
可以由全⾃动部署机制独⽴部署。
-
去中心化,服务⾃治。服务可以使⽤不同的语⾔、不同的存储技术 。
1.2.1 微服务框架功能
微服务具有以上的这些特点,那么作为⼀个微服务框架,⽐如Spring Cloud,应该具备⼀些什么功能呢?微服务框架的功能主要体现在以下⼏个⽅⾯。
-
注册中心:服务提供者和消费者,能够从注册中⼼注册和得到服务信息。
-
配置中心:在微服务架构中设计服务较多需要对于配置⽂件统⼀管理。
-
服务链路追踪:对于服务之间的负载调⽤,要能通过链路追踪,得到具体参与者,调⽤链路出现问题能够快速定位。
-
负载均衡:服务调⽤服务会采⽤⼀定的负载均衡策略,来保证服务的⾼可⽤。
-
服务容错:通过熔断、降级服务容错策略,对系统进⾏有效的保护,降级是在服务或依赖的服务异常时,返回保底数据,熔断是指依赖服务多次失效,则熔断器打开,不再尝试调⽤,直接返回降级信息。熔断后,定期探测依赖服务可⽤性,若恢复则恢复调⽤。
-
服务⽹关:⽤户请求过载时进⾏限流、排队、过载保护、⿊⽩名单、异常⽤户过滤拦截等都可以通过服务⽹关实现。
-
服务发布与回滚:蓝绿部署、灰度、AB Test等发布策略,可快速回滚应⽤。
-
服务动态伸缩、容器化:根据服务负载情况,可快速⼿动或⾃动进⾏节点增加和减少。
1.2.2 Spring Cloud简介
Spring Cloud是Spring提供的微服务框架。它利⽤Spring Boot的开发特性简化了微服务开发的复杂性,如服务发现注册、配置中⼼、消息总线、负载均衡、断路器、数据监控等,这些⼯作都可以借助Spring Boot的开发⻛格做到⼀键启动和部署。
Spring Cloud的⽬标是通过⼀系列组件,帮助开发者迅速构件⼀个分布式系统,Spring Cloud 是通过包装其它公司产品来实现的,⽐如Spring Cloud整合了开源的Netflix很多产品。Spring Cloud提供了微服务治理的诸多组件,例如服务注册和发现、配置中⼼、熔断器、智能路由、微代理、控制总线、全局锁、分布式会话等。
Spring Cloud实现微服务的治理功能产品很多,下⾯简单介绍下Spring Cloud各个产品的作⽤,以及采⽤的原则。
1.2.3 Spring Cloud版本
Spring Cloud 和Spring Boot版本对应关系如下:
Spring Cloud | Spring Boot |
---|---|
2020.0.x aka Ilford | 2.4.x, 2.5.x (Starting with 2020.0.3) |
Hoxton | 2.2.x, 2.3.x (Starting with SR5) |
Greenwich | 2.1.x |
Finchley | 2.0.x |
Edgware | 1.5.x |
Dalston | 1.5.x |
官⽹对版本进⾏如下解释
Spring Cloud Dalston、Edgware、Finchley 和 Greenwich 都已达到⽣命周期终⽌状态,不再受⽀持
因此课程采⽤的Spring Cloud,Spring Boot版本。
-
Spring Boot 2.4.9
-
Spring Cloud 2020.0.3
-
Spring Cloud Alibaba 2021.1
1.3 搭建微服务工程
接下来模拟⼀个微服务调⽤的场景,有两个微服务,⼀个订单微服务,⼀个⽀付微服务,订单微服务通过RestTepleate调⽤⽀付微服务,完成⽀付功能。
-
⽀付微服务⼯程:服务提供者
-
订单微服务⼯程:服务使⽤者
1.3.1 公共⼯程
使⽤maven创建公共⼯程“03_cloud_common”⼯程下,统⼀管理案例使⽤的实体类和⼯具类。
-
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>com.lxs.demo</groupId>
<artifactId>03_cloud_common</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
</project>
-
Payment
订单和⽀付⼯程都使⽤了Payment实体类代码如下。
package com.lxs.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Payment {
/**
* 订单编号
*/
private Integer id;
/**
* ⽀付状态
*/
private String message;
}
1.3.2 支付⼯程
使⽤Spring Initializr,创建⽀付微服务⼯程01_cloud_payment,模拟⽀付。
注意:默认https://start.spring.io,经常连不上,可以使⽤Custom:https://start.springboot.io创建。
1.选择依赖
选择下⾯依赖,如图1-8所示。
-
Spring Boot 2.4.8
-
Spring Boot DevTools
-
Lombok
-
Spring Web
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
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lxs.demo</groupId>
<artifactId>01_cloud_payment</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>01_cloud_payment</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.lxs.demo</groupId>
<artifactId>03_cloud_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.启动器
package com.lxs.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
}
3.application.yml
server:
port: 9001
4.PaymentController
在PaymentController中模拟实现⽀付功能,代码如下。
package com.lxs.demo.controller;
import com.lxs.entity.Payment;
import org.springframework.http.ResponseEntity;
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 java.util.HashMap;
@RestController
@RequestMapping("/payment")
public class PaymentController {
@GetMapping("/{id}")
public ResponseEntity<Payment> payment(@PathVariable("id") Integer id) {
Payment payment = new Payment(id, "⽀付成功");
return ResponseEntity.ok(payment);
}
}
5.启动测试
启动项⽬访问http://localhost:9001/payment/456
1.3.3 订单⼯程
1.选择依赖
选择下⾯依赖。
-
Spring Boot 2.4.8
-
Spring Boot DevTools
-
Lombok
-
Spring Web
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
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lxs.demo</groupId>
<artifactId>02_cloud_order</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>02_cloud_order</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.lxs.demo</groupId>
<artifactId>03_cloud_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.启动器
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Spring提供了⼀个RestTemplate模板⼯具类,对基于Http的客户端进⾏了封装,并且实现了对象与json的序列化和反序列化,⾮常⽅便。RestTemplate并没有限定Http的客户端类型,⽽是进⾏了抽象,⽬前常⽤的3种都有⽀持:
-
HttpClient
-
OkHttp
-
JDK原⽣的URLConnection(默认的)
3.application.yml
server:
port: 9002
4.OrderController
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/payment/{id}")
public ResponseEntity<Payment> getPaymentById(@PathVariable("id") Integer id) {
String url = "http://localhost:9001/payment/" + id;
Payment payment = restTemplate.getForObject(url, Payment.class);
return ResponseEntity.ok(payment);
}
}
5.启动并测试
启动项⽬访问http://localhost:9002/order/payment/123
1.3.4 小结和思考
简单回顾⼀下,刚才我们写了什么:
-
payment-service:对外提供了⽀付的接⼝
-
order-service:通过RestTemplate访问 http://locahost:9091/order/{id} 接⼝,调⽤⽀付服务存在什么问题?
-
在order中,我们把url地址硬编码到了代码中,不⽅便后期维护
-
order需要记忆payment-service的地址,如果出现变更,可能得不到通知,地址将失效
-
order不清楚payment-service的状态,服务宕机也不知道
-
payment-service只有1台服务,不具备⾼可⽤性
-
即便payment-service形成集群,order还需⾃⼰实现负载均衡
其实上⾯说的问题,概括⼀下就是分布式服务必然要⾯临的服务治理的问题:
-
服务管理
- 如何⾃动注册和发现
- 如何实现状态监管
- 如何实现动态路由
-
服务如何实现负载均衡
-
服务如何解决容灾问题
-
服务如何实现统⼀配置
以上的问题,我们都将在SpringCloud中得到答案。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/13441.html