代码在 https://github.com/betterGa/SpringCloudDemo
一、简介
1、集群 和 分布式
(1)集群
一台服务器无法负荷高并发的数据访问,需要设置更多的服务器一起分担压力。(一台不行就设置十台一起分担压力,十台不行就一百台 ? 。)从 物理层面 解决高并发的问题,就像 春运期间火车站 多开购票窗口,可以理解成 很多人干同一件事,来分摊压力。
(2)分布式
将一个复杂问题 拆分成 若干个 简单的小问题, 将一个大型的项目架构拆分为若干个微服务来协同完成。从 软件设计层面 解决问题, 比如 将购票分为 统计出发地与目的地、查询是否有票、统一购买票 等步骤,分别由不同的人来完成这些小的工作,最后将所有的工作结果进行整合,实现更大的需求。
2、微服务
- 什么是微服务
微服务是一种架构风格,是一种架构设计方式,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。
- 为什么需要微服务
传统开发模式下,绝大多数的 web 应用都是单体架构的风格来进行构建,这就使得所有的接口,业务逻辑层,数据持久层全部都被打包在一个 web 应用中,并且布置在一台服务器上,使得不同的模块之前也高耦合在一起,这种开发模式使得多团队协作开发的开发成本极高。
可以看到,每个团队都有自己的代码,只要维护好自己的项目,然后通过某种统一的协议,各个服务之间可以进行通信,提供对外接口 ,其他团队调用就行;项目部署起来,成为微服务,再通过微服务管理整合起来,就是整个微服务架构。这样让运维、开发、部署的工作都简化了。
- 单体应用存在的问题
(1)随着业务的发展,开发变得越来越复杂。
(2)单一功能进行修改时,需要对整个系统进行打包部署。
(3)一个模块出现问题,很可能导致整个系统崩溃。
(4)多个团队同时对数据进行操作管理,容易产生安全漏洞。
(5)各模块都使用相对统一的技术进行开发,各个模块 很难根据实际情况 选择更加合适的技术框架,局限性很大,系统的延展性比较低。
(6)模块内容过于复杂,新人上手比较费时。
其实主要就是耦合度高的问题 ?。微服务、 分布式其实是在架构结构解耦合。 解耦合这个思路在整个编程中都很重要。
- 微服务的优点
(1)各个服务的开发、测试、部署都是相互 独立 的。可以 针对某一个特定的服务 进行更多的操作,比如 负载均衡 等。
(2)当有一个新的需求加入时,传统项目需要结合各方面考虑影响,兼容性、影响都 等,微服务就不存在这样的问题,省事省力又省心,是可以自治的。
(3)使用微服务将项目拆分后,只需要保证对外接口的正常运行,大大降低了各个模块之间的耦合性,极大的提高开发效率。
- 微服务的弊端
(1)微服务的拆分基于业务,不能随心所欲的拆分,所以如何拆分,对于项目架构来说是非常重要且极具挑战的任务。
(2)涉及到服务之间的调用时,常常需要和另外一个服务的提供方进行沟通,若是两个完全不同的公司或者部门,沟通成本比较大;某服务的对外接口要进行修改,也需要与其他服务调用方进行沟通。
(3)由于各个服务相互独立,数据也是独立,当多个服务的接口进行操作时,如何保证数据的一致性是一个难点。数据统一性是微服务里面的一个难题 。
3、为什么选择 Spring Cloud
虽然微服务也有很多缺点,但是瑕不掩瑜,总体来讲,微服务还是实现分布式架构的一个非常好的方式,是当下非常热点的技术,也是未来技术发展的趋势。当下较为常见的微服务框架是 Spring Cloud 和 dubbo。那我们为什么选择 Spring Cloud 呢,原因如下:
(1)Spring Cloud 是完全基于 Spring Boot,服务调用是基于 REST API ,整合了各种成熟的产品和架构,同时基于 Spring Boot 也使得整体的开发、配置、部署都非常的方便。
(2)Spring 系列的产品具备功能齐全、简单好用、性能优美、文档规范 等优点。
可以看到,Eureka Server 是注册中心,把所有的服务都注册进来,做服务治理,然后通过 Ribbon 或者 Fein 进行负载均衡,它们都是做服务通信,Zuul 是服务网关,Hystrix 是熔断器,做服务容错,Config 做服务配置,Actuarot 做 服务监控,Zipkin 做服务跟踪,这几个都是核心组件。
二、服务治理
服务治理的核心由三部分组成:
(1)注册中心
(2)服务消费者
(3)服务提供者
就是说,在注册中心完成注册(比如 IP、端口等 )后,消费者就可以调用提供者的服务啦,可以联想 我们【消费者】在外卖平台 【注册中心】中浏览商家 【提供者】信息,然后下单,商家接下来就会配送到我们手里 。
在 分布式系统架构 中,每个微服务在启动时,会将自己的信息存储在注册中心 这个过程叫 服务注册。服务消费者 从 注册中心 获取服务提供者的信息,通过这些信息调用服务提供者的服务,这个过程叫 服务发现。
在 Spring Cloud 中,使用 Eureka 来实现服务治理。 Eureka 是 Netflix 开源的 基于 REST 的服务治理方案,Spring Cloud 集成了 Eureka,提供服务注册和服务发现的功能,可以和基于 Spring Boot 搭建的微服务应用轻松完成整合,开箱即用。
Spring Cloud Eureka 有两部分组成:
(1)Eureka Server——注册中心
(2)Eureka Client——客户端,所有要进行注册的微服务都是通过 Eureka Client 连接到 Eureka Server 完成注册的。
接下来,就来动手搭建一个 Spring Cloud 项目吧。
首先,先创建父工程,引入 parent 、Spring Boot 和 Spring Cloud 依赖,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>org.example</groupId>
<artifactId>SpringCloudDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
1、注册中心
在 父工程下新建 module eurekaServer :
引入它自己的依赖: (正是因为这个依赖 和 启动类上的 @EnableEurekaServer 注解,让它成为了 服务提供者 )
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
创建 application.yml ,配置 Eureka Server 相关信息:
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defultZone: http://localhost:8761/eureka/
属性说明
server.port:当前 Eureka Server 服务端口
eureka.client.register-with-eureka:是否将当前的 Eureka Server 服务作为客户端进行注册
eureka.client.fetch-registry:是否获取其他 Eureka Server 服务的数据
eureka.client.service-url.defaultZone:注册中心的访问地址
接下来,创建启动类,使用 @SpringBootApplication 注解声明 SpringBoot 服务入口,@EnableEurekaServer 注解声明该类是一个 EurekaServer 微服务,提供服务注册和服务发现功能,即 注册中心:
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class,args);
}
}
启动,这时报了个错:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘javax.servlet.Filter’ available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier(value=httpTraceFilter)}
根本原因是 Spring Boot 与 Spring Cloud 版本不兼容 ?,当时父工程的 spring-cloud-dependencies 中自动提示了 版本是 Finchley.SR2 ,我也就用的默认的这个,但是其实这样版本是不匹配的,SpringBoot2.2.X 及 2.3.X 集成 SpringCloud 可以用 Hoxton.SR1 。
运行结果:
可以看到,还没有服务注册过来呢。如果把eureka: client: register-with-eureka
属性改成 true,就是注册它自己,运行起来:
2、服务提供者
服务提供者、服务消费者都是需要继承 Eureka Client 的。
先创建 module,导入依赖(正是因为这个依赖,让它成为了 服务提供者 ):
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
</dependencies>
然后在 application.yaml 中进行配置:
server:
port: 8010
spring:
application:
name: provider
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
属性说明
- spring.application.name : 当前服务注册在 Eureka Server 上的名称
- eureka.client.service-url.defaultZone : 注册中心的访问地址
- eureka.instance.prefer-ip-address : 是否将当前服务的 IP 注册到 Eureka Server
创建启动类,只需要 @SpringBootApplication 注解:
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class,args);
}
}
先启动注册中心,再启动服务提供者,可以看到:
接下来,做个增删改查的实战。
我们在先 client 中创建一个实体类,需要先下载 lomback 插件, 然后 @Data 注解就可以生成 Getter、Setter、equals、hasCode、toString等方法;@AllArgsConstructor 注解可以添加一个构造函数,该构造函数含有所有已声明字段属性参数;@NoArgsConstructor 注解可以创建一个无参构造函数:
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Student {
private long id;
private String name;
private int age;
}
再新建 respository 包,提供接口:
public interface StudentRepository {
public Collection<Student> findAll();
public Student findById(long id);
public void saveOrUpdate(Student student);
public void deleteById(long id);
}
实现类:
@Repository
public class StudentRespImpl implements StudentRepository{
private static Map<Long,Student> studentMap;
static{
studentMap=new HashMap<>();
studentMap.put(1L,new Student(1L,"张三",22));
studentMap.put(2L,new Student(2L,"李四",21));
studentMap.put(3L,new Student(3L,"王五",23));
}
@Override
public Collection<Student> findAll() {
return studentMap.values();
}
@Override
public Student findById(long id) {
return studentMap.get(id);
}
@Override
public void saveOrUpdate(Student student) {
studentMap.put(student.getId(), student);
}
@Override
public void deleteById(long id) {
studentMap.remove(id);
}
}
提供控制类:(其实这里不是很规范的 REST 风格 )
@RestController
@RequestMapping("/student")
public class StudentHandler {
@Autowired
private StudentRepository studentRepository;
@GetMapping("/findAll")
public Collection<Student> findAll(){
return studentRepository.findAll();
}
@GetMapping("/findById/{id}")
public Student findById(@PathVariable("id") long id){
return studentRepository.findById(id);
}
@PostMapping("/save")
public void sava(@RequestBody Student student){
studentRepository.saveOrUpdate(student);
}
@PutMapping("/update")
public void update(@RequestBody Student student){
studentRepository.saveOrUpdate(student);
}
@DeleteMapping("/delete/{id}")
public void deleteById(@PathVariable("id") long id){
studentRepository.deleteById(id);
}
}
运行结果:
这样,就创建了一个服务提供者,接下来,需要创建一个服务消费者,实现思路 是先通过 Spring boot 搭建一个微服务应用,再通过 Eureka Client 把它注册到注册中心 Eureka Server,成为一个服务消费者。那么 服务消费者 如何调用 服务提供者 的接口呢? 用 RestTemplate。
3、服务消费者
先新建一个 module,因为 提供者 那边的方法有的返回值 是 Student 类型,所以需要先拷贝实体类 Student 。然后 导入 spring-cloud-starter-netflix-eureka-client 依赖:(正是因为这个依赖,让它成为了 服务消费者)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
然后创建配置文件:
server:
port: 8020
spring:
application:
name: consumer
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
接下来,创建启动类,同样需要创建 RestTemplate 对象:
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
还是需要拷贝实体类,然后提供控制类:
@RestController
@RequestMapping("/consumer")
public class ConsumerHandler {
@Autowired
RestTemplate restTemplate;
@GetMapping("/findAll")
public Collection<Student> findAll() {
return restTemplate.getForEntity("http://localhost:8010/student/findAll",
Collection.class).getBody();
}
@GetMapping("/findById/{id}")
public Student findById(@PathVariable("id") long id){
return restTemplate.getForEntity("http://localhost:8010/student/findById/{id}",
Student.class,id).getBody();
}
@PostMapping("/save")
public void save(@RequestBody Student student){
restTemplate.postForEntity("http://localhost:8010/student/save",
student, null).getBody();
}
@PutMapping("/update")
public void update(@RequestBody Student student){
restTemplate.put("http://localhost:8010/student/update", student);
}
@DeleteMapping("/deleteById/{id}")
public void delete(@PathVariable("id") long id){
restTemplate.delete("http://localhost:8010/student/delete/{id}",id);
}
}
这样,消费者注册到注册中心了,而且它是在访问提供者的接口,所以叫 “消费者”。这里调用 restTemplate 里的方法,之前学习 Spring Boot 的时候用过的。(博客链接?https://editor.csdn.net/md/?articleId=113433494 ) 这里如果是用 getForObject 方法,就不用调 getBody 啦。
再启动这个消费者,可以看到 :
运行结果:
其他方法也验证了一遍,都是没问题的。
三、总结
(1)集群是从物理层面,让更多台服务器分摊高并发的压力;
分布式是从软件层面,把一个大的项目拆分成若干个微服务,每个微服务是独立的,简化开发、松耦合。
(2)Spring Cloud 是微服务框架,是基于 Spring Boot 的。
(3)服务治理就是说 服务提供者、服务消费者 都在 注册中心 进行注册,然后提供者就可以调消费者的服务啦。在 Spring Cloud 中,使用 Eureka 来实现服务治理,Eureka 是基于 REST 的(也就是说遵循 HTTP 协议)。
Eureka Server 是注册中心,使用 @EnableEurekaServer 注解作用于启动类上即可声明注册中心(需要导入 spring-cloud-starter-netflix-eureka-server 依赖);
而 Eureka Client 只需要导入 spring-cloud-starter-netflix-eureka-client 依赖,启动类上只需要 @SpringBootApplication 注解即可。
提供者和消费者都需要通过 Eureka Server 注册到注册中心,而 消费者 调用 提供者通过 RestTemplate 即可。提供者和消费者没有严格的界定,就是说,A 调用 B 的接口,可以看作 A 是提供者,B 是消费者。它们从代码层面看是没有太大区别的,提供者在控制层提供了接口,其实消费者除了内部调用了提供者的接口,它也对外提供了接口(所以也可以用作服务提供者)。