1. 服务拆分和远程调用
- 任何分布式架构都离不开服务的拆分,微服务也一样。
- 服务拆分:一个单体架构按照功能模块进行拆分,变成多个服务。
微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同业务。
1.1 服务拆分原则 或 服务拆分注意事项:
- 不同的微服务,要做到单一职责(微服务拆分的目的就是单一职责),不要重复开发相同业务
- 微服务要做到数据独立,不同微服务都应该有自己独立的数据库,每个微服务都会有自己的数据库,不要访问其它微服务的数据库,做到了数据解耦。
- 微服务要对外暴露RESTful的业务接口,供其它微服务调用
1.2 实现微服务远程调用案例:
- 需求:根据订单ID查询订单的同时,把订单所属的用户信息一起返回。
- 要想实现跨服务远程调用,其实就是发送一次HTTP的请求~!
1.3 服务间远程调用方式分析
微服务的调用方式:
- 基于RestTemplate发起的HTTP请求实现远程调用!
- HTTP请求做远程调用是与语言无关的调用,只要知道对方的IP、端口、请求路径、请求参数即可!
转变为,如何在Java代码当中发送HTTP请求?
- 利用Spring提供的RestTemplate来发送HTTP请求!
因此,我们只能在order-service向user-service发起一个远程调用,发起一个HTTP的请求,调用根据ID查询用户的这个接口,远程调用的大概的步骤是这样的:
- 在需要发送远程调用模块的启动类当中注册一个RestyTemplate的实例到Spring容器
- 修改业务代码(在业务代码当中注入RestTemplate ),使用RestTemplate提供的API来发起HTTP请求:修改order-service服务中的OrderService类中的queryOrderById方法,根据Order对象中的userId查询User
- 将查询的User填充到Order对象,一起返回
1. 我们知道,Bean的注入只能放在配置类里面,而启动类就是一个配置类:
2. 在业务代码当中注入RestTemplate,使用RestTemplate提供的API来发起HTTP请求
1.4 提供者与消费者
在服务调用关系中,会有两个不同的角色:
-
服务提供者:一次业务中,被其它微服务调用的服务(提供或暴露接口给其它微服务调用)
- 服务消费者:一次业务中,调用其它微服务的服务(调用其它微服务提供的接口)
- 服务提供者与服务消费者的角色是相对的,相对于具体的业务,业务不同,角色是会变化的!
注意:
- 服务提供者与服务消费者的角色并不是绝对的,而是相对于业务而言,一个服务既可以是提供者,也可以是消费者!
- 技术要跟业务相关联!
思考:服务A调用服务B,服务B调用服务C,那么服务B是什么角色?
- 对于服务A调用服务B的业务而言,A是服务消费者,B是服务提供者
- 对于服务B调用服务C的业务而言,B是服务消费者,C是服务提供者
因此,服务B既可以是服务提供者,也可以是服务消费者!
总结:一个服务既可以是服务提供者,又可以是服务消费者!
2. Eureka注册中心
2.1 远程调用的问题
回顾:
- 订单服务(服务消费者)需要远程调用我们的用户服务(服务提供者),之前定义的的URL路径中的IP、端口、请求路径等都是硬编码,写死的,不够灵活。
假如我们的服务提供者部署了多个实例,如图:
思考:
- 服务消费者在发起远程调用的时候,该如何得知服务提供者实例的IP地址和端口?
- 有多个服务提供者实例地址,服务消费者调用时该如何选择?
- 服务消费者如何得知服务提供者的健康状态?服务消费者如何得知某个服务提供者实例是否依然健康,是不是已经宕机?
这些问题都需要利用Spring Cloud中的注册中心来解决,其中最广为人知的注册中心就是Eureka。
2.2 Eureka注册中心原理 - Eureka的结构和作用
Eureka的结构如下:
在Eureka的结构或架构当中,有两类角色(微服务角色有两类):
- 角色一:Eureka-Server注册中心(Eureka服务端):记录和管理这些微服务(记录服务信息、心跳监控)!
- 角色二:服务提供者和服务消费者,不管是服务提供者还是服务消费者,都是微服务,所以统称为Eureka的客户端 - Eureka Client端。
在Spring Cloud的生态中,采用服务注册与发现模型,来实现微服务之间的互相发现发现与调用:
如上图所示,通过在微服务系统中引入一个叫注册中心的组件,来作为协调者。
其最简化的过程是,所有的微服务应用在启动过程中会将自身包含服务名称、主机IP地址和端口号等信息发送到注册中心中,这个叫服务注册,然后上游的微服务(服务消费者)在处理请求过程中,根据服务名称到注册中心中查找对应服务的所有实例IP地址和端口号(服务发现或服务拉取)来进行服务调用,从而让分散的微服务系统之间能像一个整体一样对外提供请求处理能力。
回答之前的三个问题:
问题一:服务消费者在发起远程调用的时候,如何得知服务提供者实例的IP地址?
获取地址信息的流程如下:
- 所有的微服务应用的实例在启动过程中会将自身包含服务名称、主机IP地址和端口号等信息发送或注册到Eureka-Server注册中心(Eureka服务端)中,这个叫服务注册;
- Eureka-Server注册中心(Eureka服务端)保存服务名称到服务实例地址列表的映射关系;
- 服务消费者根据服务名称,拉取服务列表,拉取对应服务的所有实例IP地址和端口号列表,这个叫服务发现或服务拉取。
问题二:服务消费者如何从多个服务提供者实例中选择具体的实例?
- 服务消费者从实例列表中利用负载均衡算法选中一个实例地址(选中一个微服务),接着向该实例地址(微服务)发起远程调用!
问题三:服务消费者如何得知某个服务提供者实例是否依然健康,是不是已经宕机?
心跳监控
- 服务提供者(所有的微服务应用)会每隔一段时间(默认30秒)向Eureka-Server注册中心(Eureka服务端)发起或发送一次心跳请求,确认自己的健康状态,称为心跳;
- Eureka会更新服务记录列表信息:当超过一定时间没有发送心跳时,Eureka-Server注册中心(Eureka服务端)会认为该微服务实例故障,会将该实例从服务列表中剔除(心跳不正常的会被剔除);
- 这样,服务消费者在拉取服务时,就能将故障实例排除了。
注意:
- 一个微服务,既可以是服务提供者,又可以是服务发现者,因此Eureka将服务注册、服务发现等功能统一封装到了Eureka-Client(Eureka客户端)!
因此,接下来我们动手实践的步骤包括:
2.3 搭建EurekaServer
Eureka-Server - 注册中心服务端的搭建,必须创建一个独立的微服务!
在cloud-demo父工程下,创建一个子模块:
填写模块信息:
然后填写服务信息:
搭建Eureka - Server服务的步骤如下:
1. 创建项目,引入eureka-server依赖,引入Spring Cloud为eureka提供的starter依赖:spring-cloud-starter-netflix-eureka-server
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2. 编写启动类,添加@EnableEurekaServer注解(EurekaServer自动装配的开关)
package cn.itcast.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* 手动编写启动类
* 给Eureka-Server服务编写一个启动类
* 注意:一定要添加@EnableEurekaServer注解,开启Eureka的注册中心功能
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
3. 添加application.yml文件,在application.yml中配置eureka地址 或 编写下面的配置:
- Eureka自己也是一个微服务,所以Eureka在启动的时候会将自己也注册到Eureka注册中心中,这是为了将来Eureka集群之间通信去用的。
- 是为了做服务的注册采取配置的Eureka的服务名称以及Eureka的地址信息!
server:
port: 10086 #服务的端口,随便给定一个端口号
spring:
application:
name: eureka-server #服务的名称(Eureka的服务名称)
eureka:
client:
service-url: #Eureka的地址信息(如果是集群,则地址之间用逗号隔开,现在是单机)
defaultZone: http://127.0.0.1:10086/eureka
启动服务:
-
启动微服务,然后在浏览器访问:http://127.0.0.1:10086
看到下面Eureka的管理页面就算是成功了:
- 实例:一个服务每部署一份儿就是一个实例!
- Eureka会记录一个服务的所有实例!
2.4 服务注册
下面,我们将服务提供者注册到eureka-server注册中心中去,服务注册需要经过两个步骤:
1. 在服务提供者的项目中引入eureka-client依赖,引入spring-cloud-starter-netfix-eureka- client的依赖
<!-- eureka-client(Eureka客户端依赖)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2. 在application.yml文件中,添加服务名称以及配置eureka地址,编写下面的配置:
spring
application:
name: user-service #服务的名称
eureka:
client:
service-url: #Eureka的地址信息(如果是集群,则地址之间用逗号隔开,现在是单机)
defaultZone: http://127.0.0.1:10086/eureka
将服务消费者的信息也注册到EurekaServer中去,服务列表验证:
一个服务启动多个实例的操作
-
我们可以将一个服务多次启动,模拟多实例部署,但为了避免端口冲突,需要修改端口设置
首先,复制原来的服务启动配置 - 复制配置:
然后,在弹出的窗口中,填写信息(重新配置一个端口以及修改服务名称):
- -D代表就是参数了!
添加成功:
查看Erueka-Server的管理页面:
总结:
- 凡是引Eureka客户端依赖 + 配置Eureka地址就是在做服务的注册!
- 无论是服务消费者还是服务提供者,只要引入eureka-client依赖、配置eureka地址后,都可以完成服务注册!
2.5 服务发现 / 服务拉取
- 服务拉取是基于服务名称获取服务列表,然后在对服务列表做负载均衡。
下面,我们将服务消费者的逻辑修改:向eureka-server注册中心拉取服务提供者的信息,实现服务发现或服务拉取。
服务消费者完成服务拉取的步骤:
1. 在服务消费者的pom文件中,引入eureka-client依赖
- 之前说过,服务发现或服务拉取、服务注册统一都封装在eureka-client依赖,因此这一步与服务注册时一致。
<!-- eureka-client(Eureka客户端依赖)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2. 配置文件
- 服务发现或服务拉取也需要知道Eureka地址,因此第二步与服务注册一样,都是配置Eureka信息,在服务消费者的application.yml文件中,添加服务名称以及eureka地址:
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
application:
name: order-service #服务的名称
eureka:
client:
service-url: #Eureka的地址信息(如果是集群,则地址之间用逗号隔开,现在是单机)
defaultZone: http://127.0.0.1:10086/eureka
3. 服务的拉取和负载均衡
- 去eureka-server注册中心中拉取服务提供者的实例列表,并且实现负载均衡。
3.1 修改服务消费者的业务代码,修改访问的URL路径,用服务提供者的服务名称代替IP以及端口 从而实现远程调用:
3.2 在服务消费者项目的启动类中,给RestTemplate这个Bean添加负载均衡注解 - @LoadBalanced注解:
- LoadBalanced:负载均衡
重启服务消费者,清空服务提供者实例的控制台日志,看看服务消费者到底访问的是哪一个服务提供者的实例?
- 发现发起两次请求,有时候会远程调用同一个服务实例,有时候会两个服务实例都被远程调用!
Spring会自动帮助我们从eureka-server端,根据服务名称来获取服务实例列表,而后完成负载均衡!