目录
- 核心概念
- 分布式
- 分布式SOA架构
- 微服务架构
- 微服务技术对比
- 服务拆分
- 拆分思想和原则
- 创建父类工程
- 创建子工程
- 远程调用方式
- 引入RestTemplate
- CAP/BASE理论
- CAP理论
- 分区容错 (Partition-tolerance)
- 一致性(Consistency)
- 可用性(Availability)
- BASE理论
- 基本可用
- 软状态
- 最终一致性
核心概念
分布式
分布式主要有三个方向分别是分布式计算、分布式存储、分布式系统(应用)
这里我们就重点说一下分布式系统:
分布式系统一定是由多个节点组成的系统。其中,节点指的是计算机服务器,而且这些节点一般不是孤立的,而是互通的。不同的业务模块部署在不同的服务器上或者同一个业务模块分拆多个子业务,部署在不同的服务器上,解决高并发的问题,提供可扩展性以及高可用性,业务中使用分布式的场景主要有分布式存储以及分布式计算。分布式存储中可以将数据分片到多个节点上,不仅可以提高性能(可扩展性),同时也可以使用多个节点对同一份数据进行备份。
分布式环境的特点:
- 分布性:服务部署空间具有多样性。
- 并发性:程序运行过程中,并发性操作是很常见的。比如同一个分布式系统中的多个节点,同时访问一个共享资源。数据库、分布式存储。
- 无序性:进程之间的消息通信,会出现顺序不一致问题。
分布式环境下面临的问题:
- 网络通信:网络本身的不可靠性,因此会涉及到一些网络通信问题
- 网络分区(脑裂):当网络发生异常导致分布式系统中部分节点之间的网络延时不断增大,最终导致组成分布式架构的所有节点,只有部分节点能够正常通信
- 三态:在分布式架构里面多了个状态::成功、失败、超时
- 分布式事务:ACID(原子性、一致性、隔离性、持久性)
- 冷备或者热备:冷备份是对数据库进行脱机备份,在备份过程中用户无法访问数据库,但冷备份速度更快更安全。热备份也称为动态备份或在线备份,这种技术可以在系统正常运行时进行备份,但是效率较慢。
分布式SOA架构
SOA(Service-OrientedArchitecture),即面向服务的系统架构,是一个组件模型。
SOA架构主要有三种实体: service provider (服务提供者)、 service requestor (服务使用者)和 service register (服务注册中心)。这三种实体又有三种服务处理功能:Publish(发布)、Find(查找)与 Bind(捆绑)。
微服务架构
微服务(microservice)是一种软件开发技术是面向服务的体系结构(SOA)架构样式的一种变体,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于HTTP的
RESTful API)。每个服务都围绕着具体业务进行构建,并且能够独立地部署到生产环境、类生产环境等。另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据上下文,选择合适的语言、工具对其进行构建。
微服务技术对比
微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术。在国内知名度最高的就是SpringCloud和阿里巴巴的Dubbo。
- 服务集群: 主要解决高并发的,每一个服务都可能会搭建集群,每一个集群都可能有若干服务器,解决并发问题之余还是提高服务的容错率。
- 注册中心: 主要微服务管理工作。
- 配置中心: 为了保证大家更新系统性的配置的时候不用每一个服务器都更改。
- 服务网关: 请求拦截以及负载均衡。
微服务技术对比:
服务拆分
拆分思想和原则
- 单一职责:不同微服务,不要重复开发相同业务(业务拆分)
- 数据独立:不要访问其它微服务的数据库
- 面向服务:将自己的业务暴露为接口,供其它微服务调用
创建父类工程
在微服务中,我们们最多可能存在十几甚至几十个项目,如果我们每一个项目都开一个独立的idea,那么从视觉角度来看就非常不舒服,那么我们可以通过父子工程的方式去进行管理,管理工具就是我们常用的maven这样我们就可以实现maven项目的聚合。但在实际的开发中,可能就是项目独立的。·
以为这个项目需要做为父类工程使用,这样我们才能把项目聚合在一起,这个项目创建完成之后的src目录就没有用了,我们实际的业务会在各个子项目中去编写业务。所以这里我们可以直接删除。
因为这个项目我们是父工程, 将来这个项目下面会有很多子工程,所以我们需要将当前工程打包方式改为pom。
<groupId>com.its</groupId>
<artifactId>01-cloud-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 父工程需要指定打包方式为pom -->
<packaging>pom</packaging>
接下来我们还要在父工程中完成版本的管理,pom文件如下:
- SpringBoot父启动器
- 统一管理jar包版本
- 实际导入的依赖
- maven插件
<?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>
<packaging>pom</packaging>
<modules>
<module>user-service</module>
<module>order-service</module>
<module>cloud_demo_comms</module>
<module>eureka-demo</module>
<module>cloud_feignApi</module>
</modules>
<groupId>org.example</groupId>
<artifactId>cloud_demo</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 1 SpringBoot的默认默认引用的父类 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.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>
<!-- 编译时的编码 -->
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<!-- java-->
<java.version>1.8</java.version>
<!-- spring.cloud-->
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
<!-- MyBatis-plus系列 -->
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version>
<velocity-engine-core>2.2</velocity-engine-core>
<!--数据库驱动版本-->
<mysql.version>8.0.16</mysql.version>
<!--工具包-->
<hutool.version>5.7.10</hutool.version>
<!--分页工具-->
<pagehelper.version>1.3.0</pagehelper.version>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<!-- 2引入声明的依赖(引入但并不使用) 提前引入的目的是子项目再次使用可以不是书写版本号 -->
<dependencyManagement>
<dependencies>
<!-- SpringCloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<!--Maven 父子项目结构和 Java 继承一样,都是单继承,一个子项目只能制定一个父pom。
很多时候,我们需要打破这种 单继承,就使用type+import -->
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- 代码生成工具 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis-plus-generator.version}</version>
</dependency>
<!-- 代码生成模板 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>${velocity-engine-core}</version>
</dependency>
<!-- mysql数据连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!--3 直接继承的依赖,所以子项目默认继承 -->
<dependencies>
<!-- 单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
到此父类工程我们创建完成!
创建子工程
远程调用方式
引入RestTemplate
将restTemplate的实例注册到IOC容器中:
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
在order的业务层我们拿到注册的restTemplate并查询用户:
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private RestTemplate restTemplate;
/**
* 根基ID查询
* @param id
* @return
*/
@GetMapping("/{id}")
public Order byId(@PathVariable("id") Long id) {
Order order = orderService.getById(id);
User user = restTemplate.getForObject("http://localhost:8080/user/" + order.getUserId(), User.class);
order.setUser(user);
return order;
}
输出结果:
{
"id": 101,
"userId": 1,
"name": "Apple 苹果 iPhone 12 ",
"price": 699900,
"num": 1,
"user": {
"id": 1,
"username": "武松",
"address": "湖南省衡阳市"
}
}
到此我们完成了两个项目之间的调用。
CAP/BASE理论
CAP理论
分区容错 (Partition-tolerance)
大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败。比如,一台服务器放在中国,另一台服务器放在美国,这就是两个区,它们之间可能无法通信.
上图中,G1 和 G2 是两台跨区的服务器。G1 向 G2 发送一条消息,G2 可能无法收到。系统设计的时候,必须考虑到这种情况。
一般来说,分区容错无法避免,因此可以认为 CAP 的 P 总是成立。CAP 定理告诉我们,剩下的 C 和 A无法同时做到。
一致性(Consistency)
Consistency 中文叫做”一致性”。意思是,写操作之后的读操作,必须返回该值。举例来说,某条记录是v0,用户向 G1 发起一个写操作,将其改为 v1。
接下来,用户的读操作就会得到 v1。这就叫一致性。
问题是,用户有可能向 G2 发起读操作,由于 G2 的值没有发生变化,因此返回的是 v0。G1 和 G2 读操作的结果不一致,这就不满足一致性了。
为了让 G2 也能变为 v1,就要在 G1 写操作的时候,让 G1 向 G2 发送一条消息,要求 G2 也改成v1。
这样的话,用户向 G2 发起读操作,也能得到 v1。
可用性(Availability)
Availability 中文叫做”可用性”,意思是只要收到用户的请求,服务器就必须给出回应。
用户可以选择向 G1 或 G2 发起读操作。不管是哪台服务器,只要收到请求,就必须告诉用户,到底是v0 还是 v1,否则就不满足可用性。
Consistency 和 Availability 的矛盾
一致性和可用性,为什么不可能同时成立?答案很简单,因为可能通信失败(即出现分区容错)。
如果保证 G2 的一致性,那么 G1 必须在写操作时,锁定 G2 的读操作和写操作。只有数据同步后,才能重新开放读写。锁定期间,G2 不能读写,没有可用性不。
如果保证 G2 的可用性,那么势必不能锁定 G2,所以一致性不成立。
综上所述,G2 无法同时做到一致性和可用性。系统设计时只能选择一个目标。如果追求一致性,那么无法保证所有节点的可用性;如果追求所有节点的可用性,那就没法做到一致性。
BASE理论
基本可用
基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性—-注意,这绝不等价于系统不可用。比如:
(1)响应时间上的损失。正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒
(2)系统功能上的损失:正常情况下,在一个电子商务网站上进行购物的时候,消费者几乎能够顺利完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面
软状态
软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时
最终一致性
最终一致性强调的是所有的数据副本,在经过一段时间的同步之后,最终都能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。