1 系统架构的演变
随着互联网的发展,网站应用的规模不断扩大。需求激增带来技术压力。因此,系统架构也在不断演变、升级和迭代。从单一应用到垂直拆分,再到分布式服务SOA,以及热门的微服务架构,以及Google带领下来势汹涌的Service Mesh。是应该乘坐微服务船驶向远方,还是偏安一角得过且过? 事实上,生活不仅仅是现在,还有诗歌和距离。所以今天我们将回顾历史,看看系统结构的演变;把握现在,学习最热门的技术结构;展望未来,努力成为一个优秀的人Java工程师。
1.1 集中式架构
当网站流量非常小时时,只需要一个应用程序来部署所有功能,以降低部署节点和成本。此时,用于简化工作量的数据访问框架(ORM)这是影响项目开发的关键。
存在的问题:
- 开发和维护代码耦合困难
- 无法针对不同模块进行针对性优化
- 无法水平扩展
- 单点容错率低,并发能力差
1.2 垂直拆分
水平拆分: 同一个表按算法拆分,数据库中有1000个表W根据条数据,拆成10张表ID来进行拆分.
当访问量逐渐增加时,单个应用程序无法满足需求。此时,为了应对更高的并发性和业务需求,我们根据业务功能 拆分系统:
优点:
- 系统拆分实现了流量共享,解决了并发问题
- 可优化不同模块
- 水平扩展方便,负载平衡,容错率提高
缺点:
- 系统相互独立,会有很多重复的开发工作,影响开发效率
1.3 分布式服务
当垂直应用程序越来越多时,应用程序之间的互动是不可避免的。提取核心业务作为独立服务,逐步形成稳定的服务中心,使前端应用程序能够更快地响应可变的市场需求。此时,改善业务重用和集成的分布式呼叫是关键。
优点:
- 提取基本服务,相互调用系统,提高代码重用和开发效率
缺点:
- 系统间耦合度变高,调用关系复杂,难以维护
1.4 服务治理结构(SOA)
SOA :服务架构 当越来越多的服务、容量评估、小型服务资源的浪费等问题逐渐出现时,需要根据访问压力增加调度中心,实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和管理中心(SOA)是关键
以前有什么问题?
- 越来越多的服务需要管理每个服务的地址
- 调用关系复杂,难以理清依赖关系
- 服务过多,服务状态难以管理,不能根据服务情况动态管理
服务治理要做什么?
- 无需人工记录服务地址,实现服务注册中心的自动注册和发现
- 自动订阅服务,自动推送服务列表,透明呼叫服务,不关心依赖关系
- 动态监控服务状态监控报告,人为控制服务状态
缺点:
- 服务间会有依赖关系,一旦某个环节出错,影响会很大
- 服务关系复杂,运维、测试部署困难(自动化运维)
1.5 微服务
前面说的SOA,英文翻译是面向服务的。微服务,似乎也是服务,都是拆分系统的。微服务可以认为是SOA子集。所以两者很容易混淆,但实际上有一些区别: 微服务的特点:
- 单一职责:微服务中的每一项服务都对应着唯一的业务能力,实现单一职责
- 微:微服务的服务拆分粒度很小,比如用户管理可以作为服务。虽然每项服务都很小,但五脏俱全。
- 面向服务:面向服务意味着每项服务都应该暴露在外API。不关心服务的技术实现,与平台和语言无关,只要提供Rest的HTTP接口即可。
- 自治:自治是指服务间相互独立,不相互干扰
- 团队独立:每项服务都是独立的开发团队,人数不能太多。
- 技术独立:因为是面向服务,提供服务Rest接口,使用什么技术不干涉别人
- 前后分离:采用前后分离开发,提供统一Rest接口,后端不用再做了PC、开发不同接口的移动段
- 数据库分离:每个服务都使用自己的数据源
- 部署是独立的。虽然服务室已被调用,但服务重启不会影响其他服务。有利于持续集成和交付。每个服务都是独立的组件,可重复使用,可替换,减少耦合,易于维护
Dubbo跟微服务(Cloud)区别:
定位不同:Dubbo是一个RPC服务调用工具,微服务是一套完善的解决方案,Dubbo只是微服务子集
Dubbo:将Service业务层暴露,提供TCP端口,数据传输格式二进制,性能高
SpringCloud:将Controller层handler方法暴露,提供RestFul Http接口。数据传输格式为。Json
2 服务调用方式
2.1 RPC和HTTP
无论是微服务还是微服务SOA,都面临着服务间的远程调用。那么服务间的远程调用方法有哪些呢? 有两种常见的远程调用方法:
- RPC: Remote Produce Call远程过程调用,类似和RMI(remote method invoke)。基于原生的自定义数据格式TCP通信速度快,效率高。webservice(apache CXF),现在热门的dubbo,都是RPC典型代表。
- Http: http基于网络传输协议实际上是一种网络传输协议TCP,规定了数据传输的格式。 目前客户端浏览器和服务端通信基本采用Http协议也可用于远程服务调用。缺点是新闻包装臃肿(解决方案:当数据量大时,请求响应数据压缩),其优点是服务提供商和调用方没有技术限制,自由灵活,更符合微服务的概念。现在热门的Rest风格,可以通过http实现协议。
如果你们公司全部采用,如果你们公司全部采用Java然后使用技术栈Dubbo微服务架构是不错的选择。 相反,如果公司的技术栈多样化,你更喜欢Spring家族,那么SpringCloud构建微服务是最好的选择。我们将选择我们的项目SpringCloud所以我们会将使用套件Http实现服务间调用的方式。
2.2 Http客户端工具
既然微服务选择了Http,然后我们需要考虑自己来处理请求和响应。然而,开源世界已经有了很多 多的http客户端工具可以帮助我们做这些事情,例如:
- HttpClient
- OKHttp
- URLConnection
3 RestTemplate
RestTempalte是Spring Boot封装好的Http客户端工具。
步骤:
1. 添加服务提供者,提供者Rest HTTP接口 2. 通过添加服务调用器,添加服务调用器RestTemplate访问服务提供商的接口 3. 服务提供者和调用者都放在父工程中
3.1 搭建父工程
为方便项目管理,将服务提供商和服务调用者放入同一项目
新建父工程spring-cloud-parent-demo
父工程不需要写代码,可以src目录删除
添加Spring Boot依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId
>
spring-boot-starter-parent</artifactId>
<version>2.2.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
3.2 搭建服务提供者
项目结构如下:
开发步骤:
-
添加子模块user-service,添加web依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
-
添加配置文件application.yml
server: port: 8001 spring: application: # 当前应用的服务名称(也叫服务ID) name: user-service jackson: time-zone: GMT+8
-
添加启动类
package com.itheima; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class UserApplication { public static void main(String[] args) { SpringApplication.run(UserApplication.class, args); } }
小技巧:每次写启动类都是固定格式,可以自定义快速启动类模板:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class $ClassName$ { public static void main(String[] args) { SpringApplication.run($ClassName$.class,args); } }
模板应用范围定义
选择Java
编辑模板变量:
-
添加实体
package com.itheima.user.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; @Data @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private Integer age; private Date updateTime; }
-
编写Controller
package com.itheima.user.controller; import com.itheima.user.entity.User; 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.Date; @RestController @RequestMapping("/user") public class UserController { /** * 根据用户ID获取用户 * 接口地址: http://localhost:8001/user/1 * @param id * @return */ @GetMapping("/{id}") public User getById(@PathVariable("id") Long id){ User user = new User(id,"tom",18,new Date()); return user; } }
-
启动项目,打开http://localhost:8001/user/1
3.3 搭建服务消费者
-
添加子模块service-consumer,添加web依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
-
添加配置文件application.yml
server: port: 9001 spring: application: # 当前应用的服务名称(也叫服务ID) name: service-consumer jackson: time-zone: GMT+8
-
添加启动类
package com.itheima; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }
-
添加实体
@Data @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String name; private Integer age; private Date updateTime; }
-
编写Controller
@RestController @RequestMapping("/consumer") public class ConsumerController { @Autowired // 注入RestTemplate模板工具 private RestTemplate restTemplate; @GetMapping("/user/{id}") public User getUserById(@PathVariable("id") Long id) { // 通过远程调用user-service提供的HTTP接口 String url = "http://localhost:8001/user/" + id; // 第一个参数是接口调用地址 第二个参数是返回的类型 User user = restTemplate.getForObject(url, User.class); return user; } }
-
访问http://localhost:9001/consumer/user/1
3.4 微服务调用问题
user-service:对外提供接口服务
service-consumer: 通过RestTemplate
访问 http://locahost:8001/ 调用接口服务
存在问题:
- 在consumer中,我们把url地址硬编码到了代码中,不方便后期维护
- consumer需要记忆user-service的地址,如果出现变更,可能得不到通知,地址将失效
- consumer不清楚user-service的状态,服务宕机也不知道
- user-service只有1台服务,不具备高可用性
- 即便user-service形成集群, consumer还需自己实现负载均衡
其实上面说的问题,概括一下就是分布式服务必然要面临的问题:
- 服务管理
- 如何自动注册和发现服务
- 如何实现服务状态监管
- 如何实现服务动态路由
- 服务如何实现负载均衡
- 服务如何解决容灾问题
- 服务如何实现统一配置
以上的问题,我们都将在Spring Cloud中得到解决。
4. Spring Cloud
4.1 简介
Spring Cloud是一个基于 Spring Boot实现的微服务架构开发工具(SpringCloud开发必须有springboot环境)。它为微服务架构中涉及的配置管理、服务治理、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。 Spring Cloud包含了多个子项目(针对分布式系统中涉及的多个不同开源产品,还可能会新增),如下所述。
- Spring Cloud Config:配置管理工具,支持使用Git存储配置内容,可以使用它实现应用配置的外部化存储,并支持客户端配置信息刷新、加密/解密配置内容等。
- Spring Cloud Netflix:核心组件,对多个 Netflix OSS开源套件进行整合
- Eureka:服务治理组件,包含服务注册中心、服务注册与发现机制的实现。
- Ribbon:客户端负载均衡的服务调用组件。
- Hystrix:容错管理组件,实现断路器模式,帮助服务依赖中出现的延迟和为故障提供强大的容错能力。
- Feign:基于 Ribbon和 Hystrix的声明式服务调用组件。
- Gateway: 网关组件,提供智能路由、访问过滤等功能。
- Spring Cloud Bus: 事件、消息总线,用于传播集群中的状态变化或事件,以触发后续的处理,比如用来动态刷新配置等。
官网:https://spring.io/projects/spring-cloud
4.2 版本说明
Spring Cloud不像 Spring社区其他一些项目那样相对独立,它是一个拥有诸多子项目的大型综合项目,可以说是对微服务架构解决方案的综合套件组合,其包含的各个子项目也都独立进行着内容更新与迭代,各自都维护着自己的发布版本号。因此每一个Spring Cloud的版本都会包含多个不同版本的子项目,为了管理每个版本的子项目清单,避免 Spring Cloud的版本号与其子项目的版本号相混淆,没有采用版本号的方式,而是通过命名的方式。
这些版本的名字采用了伦敦地铁站的名字,根据字母表的顺序来对应版本时间顺序,比如最早的 Release版本为 Angel,第二个 Release版本为 Brixton
当一个版本的 Spring Cloud项目的发布内容积累到临界点或者一个严重bug解决可用后,就会发布一个“service releases”版本,简称SRX版本,其中X是一个递增的数字,所以 Brixton.SR5就是 Brixton的第5个 Release版本
Spring Cloud Version | Spring Boot Version |
---|---|
2020.0.x aka Ilford | 2.4.x |
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 |
5.服务治理
5.1 认识Eureka
首先我们来解决第一问题,服务的管理。
问题分析
在刚才的案例中, user-service对外提供服务,需要对外暴露自己的地址。而consumer(调用者)需要 记录服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么, 但是在现在日益复杂的互联网环境,一个项目肯定会拆分出十几,甚至数十个微服务。此时如果还人为 管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦。
解决方案-网约车
这就好比是 网约车出现以前,人们出门叫车只能叫出租车。一些私家车想做出租却没有资格,被称为黑 车。而很多人想要约车,但是无奈出租车太少,不方便。私家车很多却不敢拦,而且满大街的车,谁知 道哪个才是愿意载人的。一个想要,一个愿意给,就是缺少引子,缺乏管理啊。 此时滴滴这样的网约车平台出现了,所有想载客的私家车全部到滴滴注册,记录你的车型(服务类 型),身份信息(联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。 此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合 需求的车到你面前,为你服务,完美!
Eureka能做什么?
Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己 的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。 同时,服务提供方与Eureka之间通过 “心跳” 机制进行监控,当某个服务提供方出现问题, Eureka自然 会把它从服务列表中剔除。 这就实现了服务的、。
5.2 基本架构
架构图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZeAO8UfS-1626875997674)(assets/image-20200523223318698.png)]
基本概念
- Eureka-Server:就是服务注册中心(可以是一个集群),对外暴露自己的地址。
- 提供者:启动后向Eureka注册自己信息(地址,服务名称等),并且定期进行服务续约
- 消费者:服务调用方,会定期去Eureka拉取服务列表,然后使用负载均衡算法选出一个服务进行调用。
- 心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态
5.3 快速入门
步骤:
0. 在父工程中添加SpringCloud的版本依赖管理
1. 搭建注册中心
a. 新建工程,添加Eureka server依赖
b. 配置注册中心服务地址
c. 添加启动类
2. 服务提供者到注册中心注册
a. 添加Eureka client 依赖
b. 配置文件中添加注册中心地址
c. 在启动类上添加服务发现注解
3. 服务调用者到注册中心注册
a. 添加Eureka client 依赖
b. 配置文件中添加注册中心地址
c. 在启动类上添加服务发现注解
d. 采用服务的方式调用服务提供者的接口
5.3.1 搭建服务注册中心
- 在父工程pom.xml中添加Spring Cloud的依赖管理
<properties>
<spring-cloud.version>Hoxton.SR6</spring-cloud.version>
</properties>
<!--版本锁定:当前父工程下所有子模块使用springCLoud组件版本由 Cloud版本决定 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 创建一个Spring Boot工程,命名为eureka-server,并在pom.xml中添加eureka依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
-
添加配置文件application.yml
server: port: 1111 spring: application: name: eureka-server eureka: client: service-url: defaultZone: http://127.0.0.1:${ server.port}/eureka # 注册中心的职责是维护服务实例,不需要去检索服务 fetch-registry: false # 默认设置下,注册中心会将自己作为客户端来尝试注册自己,设置为false代表不向注册中心注册自己 register-with-eureka: false
-
添加启动类
package com.itheima; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer //开启eureka注册中心 public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
启动应用并访问:http://localhost:1111 可以看到Eureka界面
5.3.2 服务注册
注册服务,就是在服务上添加Eureka的客户端依赖,客户端代码会自动把服务注册到EurekaServer中。
- 在user-service中添加Eureka客户端依赖:
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 在启动类上 通过添加
@EnableDiscoveryClient
来开启Eureka客户端功能
package com.itheima;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
//@EnableEurekaClient //适用于eureka作为注册中心
@EnableDiscoveryClient //适用于eureka,zookeeper,nacos,consul
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
- 配置文件中添加注册中心地址
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:1111/eureka
这里spring.application.name属性指定应用名称,将来会作为服务的id使用。
日志中出现registration status: 204
表示注册成功:
此时再打开注册中心的地址:http://localhost:1111,可以看到服务已经注册上去了
5.3.3 服务发现与消费
接下来我们修改service-consumer,尝试从EurekaServer获取服务。 方法与消费者类似,只需要在项目中添加EurekaClient依赖,就可以通过服务名称来获取信息了
- 在service-consumer中添加Eureka客户端依赖:
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 在启动类上 通过添加
@EnableDiscoveryClient
来开启Eureka客户端功能
package com.itheima;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] a