# 二、服务拆分及远程调用🎄

  • 服务拆分
  • 服务间调用

# 2.1、服务拆分注意事项🌳

  1. 不同微服务,不要重复开发相同业务
  2. 微服务数据独立,不要访问其它微服务的数据库
  3. 微服务可以将自己的业务暴漏为接口,供其它微服务调用

image-20231004140816014

前往查看代码:

https://gitee.com/doukaixin/typora/tree/cloud-demo 代码演示 /

查询的结果:

user

image-20231004160050762

order

image-20231004160120577

总结

  1. 微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同业务
  2. 微服务可以将业务暴漏为接口,供其它微服务使用
  3. 不同微服务都应该有自己独立的数据库

# 2.2、微服务远程调用🌳

案例:根据订单 id 查询订单功能

需求:根据订单 id 查询订单的同时,把订单所属的用户信息一起返回

image-20231004154028321

# 2.2.1、远程调用方式分析🌲

现在 user 服务对外暴漏了一个 RestFull 的接口,只要我们在 url 中输入对应的地址就一定能拿到用户信息

image-20231004154213334

image-20231004154408769

如何在 java 中发送 http 请求呢?

# 2.2.2、操作步骤演示:🌲

# 一、注册 RestTemplate🌴

在 order-service 的 OrderApplication ,SpringBoot 启动类中注册 RestTemplate

/**
* 创建 RestTemplate 并注入 Spring 容器
*/
@Bean
public RestTemplate restTemplate()
{
   return new RestTemplate();
}
# 二、修改方法🌴

修改 order-service 中的 OrderService 的 queryOrderById 方法

@Service
public class OrderService {
    @Autowired
    private RestTemplate restTemplate;
   
    public Order queryOrderById(Long orderId) {
        // 1. 查询订单
        Order order = orderMapper.findById(orderId);
        // 2. 利用 RestTemplate 发起 http 请求,查询用户
        // 2.1 url 路径
        String url = "http://localhost:8081/user/" + order.getUserId();
        // 2.2 发送请求,实现远程调用
        // 默认返回 json 数据类型,我们可以指定返回的类型为 user 对象类型
        User user = restTemplate.getForObject(url, User.class);
        // 3. 封装 User 对象到 Order
        order.setUser(user);
        // 4. 返回
        return order;
    }
}

总结

  1. 微服务调用方式
    • 基于 RestTemplate 发起的 http 请求实现远程调用
    • http 请求做远程调用是与语言无关的调用,只要知道对方的 ip,端口,接口路径,请求参数即可

# 2.2.3、提供者与消费者🌳

  • 服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
  • 服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)

image-20231004161322330

思考问题

服务 A 调用服务 B,服务 B 调用服务 C,那么服务 B 是什么角色?

服务 B 是什么角色要看相对于谁而言,如果相对于 A 调 B 那它就是提供者,相对 B 调 C 它又变成了消费者

因此我们可以认为一个服务它既可以是提供者也可以是消费者

总结

  1. 服务调用关系
    • 服务提供者:暴漏接口给其它微服务调用
    • 服务消费者:调用其它微服务提供的接口
    • 提供者与消费者 角色其实是相对
    • 一个服务可以同时服务提供者和服务消费者

# 2.3、Eureka 注册中心🌳

  • 远程调用的问题
  • eureka 原理
  • 搭建 EurekaServer
  • 服务注册
  • 服务发现

服务调用出现的问题

  • 服务消费者该如何获取服务提供者的地址信息?
  • 如果有多个服务提供者,消费者该如何选择?
  • 消费者如何得知服务提供者的健康状态?

image-20231004164239712

# 2.3.1、Eureka 的作用🌲

在 Eureka 的结构当中,分成了两个 (概念 / 角色)。第一个角色就是 Server 服务端它的名字叫做 “注册中心” 其作用是:记录管理这些微服务.

而另外第二个角色就是 user-service 服务提供者 order-service 服务消费者,不管是提供者还是消费者都是微服务所以统称为:Eureka 的客户端.

image-20231004164706564

user-service 服务提供者启动时会将自己的信息注册给 Eureka (每一个服务启动时都会做这件事,只要是 Eureka 的客户端)

image-20231004164916779

order-service 服务消费者找 Eureka 去查询一下有没有 user-service,然后 Eureka 查询到的话就返回给 order-service 关于 user-service 的地址信息.

image-20231004165136110

现在我们拿到列表服务的信息了,这时就由负载均衡从三个地址信息里面挑出一个然后进行请求.

image-20231004165416209

那么问题又来了,挑出来的这个会不会是 <font color='red'> 挂掉 </font > 的呢?

:不会!因为 user-service 服务每隔 30 秒都会想 Eureka 发送一次心跳,来确认一下自己的状态,如果它不跳了 Eureka 就会将 user-service 从列表中移除掉.

image-20231004165753458

eureka 的作用.

  • 消费者该如何获取服务提供者具体信息?
    • 服务提供者启动时向 eureka 注册自己的信息
    • eureka 保存这些信息
    • 消费者根据服务名称向 eureka 拉取提供者信息
  • 如果有多个服务提供者,消费者该如何选择?
    • 服务消费者利用负载均衡算法,从服务列表中挑选一个
  • 消费者如何感知服务提供者健康状态?
    • 服务提供者会每隔 30 秒向 EurekaServer 发送心跳请求,报告健康状态
    • eureka 会更新记录服务列表信息,心跳不正常会被剔除
    • 消费者就可以拉取到最新的信息

总结

在 Eureka 架构中,微服务角色有两类:

  • EurekaServer:服务端,注册中心
    • 记录服务信息
    • 心跳监控
  • EurekaClient:客户端
    • provider (提供者):服务提供者,例如案例中的 user-service
      • 注册自己的信息到 EurekaServer
      • 每隔 30 秒向 EurekaServer 发送心跳
    • consumer (消费者):服务消费者,例如案例中的 order-service
      • 根据服务名称从 EurekaServer 拉取服务列表
      • 基于服务列表做负载均衡,选中一个微服务后发起远程调用

# 2.3.2、动手实践🌲

image-20231004171046625

# 2.3.2.1 、搭建 EurekaServer🌴

搭建 EurekaServer 服务步骤如下:

1、创建项目,引入 spring-cloud-starter-netflix-eureka-server 的依赖

这里的版本号由父模块进行了统一管理,这里出错了可能是版本与 SpringBoot 不对应的问题需要降低某一方或升级某一方的版本号

<dependencies>
   <!--eureka 服务端 -->
   <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
   </dependency>
</dependencies>

2、编写启动类,添加 @EnableEurekaServer 注解

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
	public static void main(String[] args) {
		SpringApplication.run(EurekaServerApplication.class, args);
	}
}

3、添加 appliation.yml 文件,编写下面的配置:

server:
  port: 10086 #服务端口
# 为了做服务注册才配的信息
spring:
  application:
    name: eurekaserver #服务名称
eureka:
  client:
    service-url: #eureka 地址信息
      defaultZone: http://127.0.0.1:10086/eureka
# 自己就有 eureka 为什么还要配置地址信息呢?
# 原因:eurka 自己也是一个微服务,所以 eureka 在启动的时候会将自己注册到 eureka 中
#      这是为了将来 eureka 集群之间通信去用的,比方说启动了三个 eureka 将来这三个
#      eureka 之间会相互注册,这样它们就可以做数据交流了。所以 defaultZone 应该
#      配置的是 eureka 集群的地址如果有多个使用逗号隔开

启动进行访问测试是否正常

image-20231004174930549

启动正常。这里面最重要的在于如下:

每个实例的状态

image-20231004175000766

总结

  1. 搭建 EurekaServer
    • 引入 eureka-server 依赖
    • 添加 @EnableEurekaServer 注解
    • 在 application.yml 中配置 eureka 地址
# 2.3.2.2、注册 user-service🌴

将 user-service 服务注册到 EurekaServer 步骤如下:

1、在 user-service 项目引入 spring-cloud-starter-netflix-eureka-client 的依赖

<!--eureka 客户端依赖 -->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2、在 appliation.yml 文件,编写下面的配置:

server:
  port: 8081
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: dkx.
    driver-class-name: com.mysql.cj.jdbc.Driver
  application:
    name: userservice # user 服务名称
mybatis:
  type-aliases-package: cn.itcast.user.pojo
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
eureka:
  client:
    service-url: # eureka 地址信息
      defaultZone: http://127.0.0.1:10086/eureka

操作完成!

如果要将服务消费者注册到 eureka 中 将同样的方法配置到 order-service 中然后访问 eureka 地址

image-20231004181537727

另外,我们可以将 user-service 多次启动,模拟多实例部署,但为了避免端口冲突,需要修改端口设置:-Dserver.port=8082

image-20231004190504048

设置好后运行 user 的新服务再次访问就会出现两个实例

image-20231004192115170

总结

  1. 服务注册
    • 引入 eureka-client 依赖
    • 在 application.yml 中配置 eureka 地址
  2. 无论是消费者还是提供者,引入 eureka-client 依赖,知道 eureka 地址后,都可以完成服务注册.
# 2.3.2.3、在 order-service 完成服务拉取🌴

服务拉取是基于服务名称获取服务列表,然后在对服务列表负载均衡

1、修改 OrderService 的代码,修改访问的 url 路径,用服务名代替 ip,端口:

String url = "http://userservice/user/" + order.getUserId();

2、在 order-service 项目的启动类 OrderApplication 中的 RestTemplate 添加负载均衡注解:

@Bean
@LoadBalanced
public RestTemplate restTemplate()
{
   return new RestTemplate();
}

访问 order 地址

image-20231004193719047

在 order 与 user 的控制台中就会打印查询的信息

image-20231004193801716

总结

  1. 搭建 EurekaServer
    • 引入 eureka-server 依赖.
    • 启动类中添加 @EnableEurekaServer 注解
    • 在 application.yml 中配置 eureka 地址.
  2. 服务注册
    • 引入 eureka-client 依赖.
    • 在 application.yml 中配置 eureka 地址.
  3. 服务发现 / 拉取
    • 引入 eureka-client 依赖.
    • 在 application.yml 中配置 eureka 地址.
    • 启动类中的 Bean。给 RestTemplate 添加 @LoadBalanced 注解
    • 用服务提供者的服务名称远程调用