Part04 ☀️
Part04 ☀️
课程内容
- 服务的注册和发现 🍐
- 注册中心的原理 🍐 ✏️
- Nacos注册中心 🍐 ✏️
- 服务注册 🍐 ✏️
- 服务发现
- 负载均衡
- GateWay网关
- 路由
- 断言
1.服务注册和发现 🍐
1.1.注册中心原理🍐
注册中心是服务治理中的重要组件,用于管理服务实例的数量和状态,提供服务注册和发现的功能。🎯
注册中心原理
在上一章我们实现了微服务拆分,目前有商品微服务和购物车微服务,并且通过 Http 请求实现了跨微服务的远程调用。不过这种手动发送 Http 请求的方式存在一些问题。
试想一下,假如商品微服务被调用较多,为了应对更高的并发,我们进行了多实例部署,如图:

此时,每个 item-service
的实例其 IP 或端口不同,问题来了 :

- item-service 这么多实例,cart-service 如何知道每一个实例的地址?
- http 请求要写 url 地址,
cart-service
服务到底该调用哪个实例呢? - 如果在运行过程中,某一个
item-service
实例宕机,cart-service
依然在调用该怎么办? - 如果并发太高,
item-service
临时多部署了N台实例,cart-service
如何知道新实例的地址?
为了解决上述问题,就必须引入注册中心的概念了,接下来我们就一起来分析下注册中心的原理。🎯
在微服务远程调用的过程中,包括两个角色:
- 服务提供者:提供接口供其它微服务访问,比如
item-service
- 服务消费者:调用其它微服务提供的接口,比如
cart-service
在大型微服务项目中,服务提供者的数量会非常多,为了管理这些服务就引入了注册中心的概念。注册中心、服务提供者、服务消费者三者间关系如下:

流程如下: 👇
- 服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心
- 调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1 个服务可能多实例部署)
- 调用者自己对实例列表负载均衡,挑选一个实例
- 调用者向该实例发起远程调用
当服务提供者的实例宕机或者启动新实例时,调用者如何得知呢? 👇
- 服务提供者会定期向注册中心发送请求,报告自己的健康状态(心跳请求)
- 当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除
- 当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表
- 当注册中心服务列表变更时,会主动通知微服务,更新本地服务列表
总结
- 服务治理中的三个角色分别是什么?
- 服务提供者:暴露服务接口,供其它服务调用
- 服务消费者:调用其它服务提供的接口
- 注册中心:记录并监控微服务各实例状态,推送服务变更信息
- 消费者如何知道提供者的地址?
- 服务提供者会在启动时注册自己信息到注册中心,消费者可以从注册中心订阅和拉取服务信息
- 消费者如何得知服务状态变更?
- 服务提供者通过心跳机制向注册中心报告自己的健康状态,当心跳异常时注册中心会将异常服务剔除,并通知订阅了该服务的消费者
- 当提供者有多个实例时,消费者该选择哪一个?
- 消费者可以通过负载均衡算法,从多个实例中选择一个
课堂作业
- 什么是注册中心?他有什么功能?🎤
- 服务的提供者向注册中心提供什么信息?🎤
- 服务的调用者从注册中心获取什么信息?🎤
- 点击图片,补充空白区域的内容
1.2.认识和安装Nacos ✏️
国内公司一般都推崇阿里巴巴的技术,比如注册中心,SpringCloudAlibaba也推出了一个名为Nacos的注册中心。
认识和安装Nacos
Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。

:::
安装教程码操作
1.1.下载安装包
在Nacos的GitHub(国外的代码仓库)页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
- GitHub主页:https://github.com/alibaba/nacos
- GitHub的Release下载页:https://github.com/alibaba/nacos/releases
- 如图:
image-20210402161102887
本课程采用1.4.1.版本的Nacos,课前资料已经准备了安装包:

windows版本使用nacos-server-1.4.1.zip
包即可。
1.2.解压 👇
将这个包解压到任意非中文 目录下,如图:

启动非常简单,双击点击启动.bat即可

1.3.端口配置 👇
Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。
如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件中的端口:

修改其中的内容:

1.4.访问 👇
在浏览器输入地址:http://127.0.0.1:8848/nacos

默认的账号和密码都是nacos
,进入后:

总结
课堂作业
- nacos是什么?有什么作用?如果让你打比方,你角色Nacos像生活中的什么东西?🎤
1.3.服务注册 ✏️
服务注册

接下来,我们把 item-service
和cart-service
注册到 Nacos,🎯 步骤如下:
- 引入依赖
- 配置 Nacos 地址
- 重启
代码操作
1.添加依赖
在 item-service
和cart-service
的 pom.xml
中都添加依赖:
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.配置 Nacos
在 item-service
和cart-service
的 application.yml
中添加 nacos 地址配置:
spring: # 顶格写
application:
name: item-service # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
spring: # 顶格写
application:
name: cart-service # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
配置后,启动2个微服务,并且访问 http://127.0.0.1:8848/nacos
用户名和密码都是nacos

实例注册成功
3.启动服务实例
为了测试一个服务多个实例的情况,我们再配置一个 item-service
的部署实例:

然后配置启动项,注意重命名并且配置新的端口,避免冲突:

重启 item-service
的两个实例:

访问 nacos 控制台,可以发现服务注册成功:

点击详情,可以查看到 item-service
服务的两个实例信息:

🎉恭喜你🎉,掌握了nacos服务注册,距离微服务调用越来越近了
总结
课堂作业
- 为什么要进行服务注册?🎤
- 向注册中心注册服务,主要要配置哪些信息?🎤
- 思考一下cart-service远程调用item-service,道理调用哪台服务器尼??
2.网关路由
2.1.认识网关 🍐
认识网关
网关:就是网络的关口,负责请求的路由、转发、身份校验。
数据在网络间传输,从一个网络传输到另一网络时就需要经过网关来做数据的路由和转发以及数据安全的校验。
更通俗的来讲,网关就像是以前园区传达室的大爷。
- 外面的人要想进入园区,必须经过大爷的认可,如果你是不怀好意的人,肯定被直接拦截。
- 外面的人要传话或送信,要找大爷。大爷帮你带给目标人。
现在,微服务网关 就起到同样的作用。前端请求不能直接访问微服务,而是要请求网关 :
- 网关可以做安全控制,也就是登录身份校验,校验通过才放行
- 通过认证后,网关再根据请求判断应该访问哪个微服务,将请求转发过去

在 SpringCloud 当中,提供了两种网关实现方案:
- Netflix Zuul
[zuːl]
:早期实现,目前已经淘汰 - SpringCloudGateway:基于 Spring 的 WebFlux 技术,完全支持响应式编程,吞吐能力更强
课堂中以 SpringCloudGateway 为例来讲解,官方网站:
https://spring.io/projects/spring-cloud-gateway/

总结
课堂作业
- 什么是网关?有何作用?🎤
- SpringCloud提供了哪些网关解决方案?哪种方案更受欢迎?🎤
2.2 Gateway快速入门 🍐 ✏️
SpringCloudGateway快速入门
接下来,先看下如何利用网关实现请求路由 。由于网关本身也是一个独立的微服务 ,因此也需要创建一个模块开发功能。大概步骤如下:
- 创建网关微服务
- 引入 SpringCloudGateway、NacosDiscovery 依赖
- 编写启动类
- 配置网关路由(可以理解为路牌)

代码操作
步骤
- 创建hm-gateway微服务
- 导入网关依赖,并且在com.hmall.gateway下创建GatewayApplication启动类
- 在
hm-gateway
模块的resources
目录新建一个application.yaml
文件,配置服务名字和nacos配置以及路由信息 - 启动网关微服务(端口为8080),观察nacos中是否注册成功,以及访问前端http://localhost:18080 是否能同时登录和查看购物车列表
1.创建项目
首先,我们要在 hmall 下创建一个新的 module,命名为 hm-gateway,作为网关微服务:


创建完成后,程序的结构
2.引入依赖
在 hm-gateway
模块的 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>
<parent>
<groupId>com.heima</groupId>
<artifactId>hmall</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>hm-gateway</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
启动类
在 hm-gateway
模块的 com.hmall.gateway
包下新建一个启动类:

代码如下:
package com.hmall.gateway;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
3.配置路由
接下来,在 hm-gateway
模块的 resources
目录新建一个 application.yaml
文件,内容如下:
server: # 顶格写
port: 8080
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 127.0.0.1:8848
gateway:
routes:
- id: item # 路由规则id,自定义,唯一
uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
- Path=/items/**,/search/**,/users/** # 这里是以请求路径作为判断规则
- id: cart
uri: lb://cart-service
predicates:
- Path=/carts/**
- After=2024-06-22T15:14:47.433+08:00[Asia/Shanghai]
4.测试

启动 GatewayApplication
和HMallmApplication
,以 http://localhost:8080 拼接微服务接口路径来测试。例如:
http://localhost:8080/items?ids=100001964366

打开nacos观察是否注册成功! http://localhost:8848/nacos

此时,启动 UserApplication
、CartApplication
,然后打开前端页面http://localhost:18080,发现相关功能都可以正常访问了:

3. 负载均衡 🍐
3.1 服务内部的负载均衡 🍐
负载均衡
项目整体的架构:👇

目前有商品微服务和购物车微服务,并且通过 Http 请求实现了跨微服务的远程调用。不过这种手动发送 Http 请求的方式存在一些问题。
试想一下,假如商品微服务被调用较多,为了应对更高的并发,我们进行了多实例部署 ,如图:

此时,每个 item-service
的实例其 IP 或端口不同,问题来了 :

- item-service 这么多实例,cart-service 如何知道每一个实例的地址?
- http 请求要写 url 地址,
cart-service
服务到底该调用哪个实例呢? - 如果在运行过程中,某一个
item-service
实例宕机,cart-service
依然在调用该怎么办? - 如果并发太高,
item-service
临时多部署了N台实例,cart-service
如何知道新实例的地址?
为了解决上述问题,就必须引入注册中心的概念了,接下来我们就一起来分析下注册中心的原理。🎯
问题1的解决方案
步骤
- 部署3台商品服务器,端口为8081,8082,8083(购物车端口为:8088)
- 调用DiscoveryClient对象,从nacos中获得商品服务器列表
- 利用随机数算法,随机选取一台服务器,作为调用的商品服务器实现负载均衡!!!
- 测试是否达到预期
- 参考下列教程,部署3台商品服务器,端口为8081,8082,8083(购物车端口为:8088)
部署结果如下:👇
调用DiscoveryClient对象,从nacos中获得商品服务器列表
利用随机数算法,随机选取一台服务器,作为调用的商品服务器实现负载均衡!!!

代码(重点为阴影部分)
/**
* 处理购物车数据,调用远程服务获取最新价格、库存、状态
* @param cartVOS
*/
private void handleCartItems(List<CartVO> cartVOS) {
//商品服务的请求连接是:http://localhost:8080/items?ids=100001964366,100000003145
//1. 模拟浏览器发起http请求,获取数据
//1.1 拼接参数:100001964366,100000003145 字符串生成
//将cartVOS列表中的对象,提取出itemId字段,然后拼接成11,22,33这样的字符串,使用stream进行实现
String ids = cartVOS.stream()
.map(CartVO::getItemId)
.map(String::valueOf)
.collect(Collectors.joining(","));
log.info("商品的ids:{}",ids);
//从注册中心中发现item-service服务列表
List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
//利用负载均衡策略,选一个实例
ServiceInstance serviceInstance = instances.get(RandomUtil.randomInt(instances.size()));
URI uri = serviceInstance.getUri();
log.info("当前调用的商品服务器是:{}", uri);
// 2.todo 查询商品
String url = uri+"/items?ids=" + ids;
//2. 使用restTemplate发起http请求,获取数据
Item[] items =restTemplate.getForObject(url, Item[].class);
//3. 判断是否为空
if (ArrayUtil.isEmpty(items)){
return;
}
//4.将数组转成集合,至此通过商品id获取了最新的商品列表,里面的价格、库存、状态是最新的
List<Item> itemList = Arrays.asList(items);
//todo 5. 将itemList的数据,赋值给cartVOS
//5.1 将itemList转成Map<Long,Item>,key是itemId,value是Item对象
// 1,商品对象1
// 2, 商品对象2
Map<Long, Item> itemMap = itemList.stream()
.collect(Collectors.toMap(Item::getId, Function.identity()));
//5.2 遍历cartVOS,获取每一个cartVO,然后获取cartVO中的itemId,然后根据itemId从itemMap中获取Item对象,然后赋值给cartVO
for (CartVO cartVO : cartVOS) {
//通过itemid,从map中获取最新的商品对象
Item newitem = itemMap.get(cartVO.getItemId());
//如果为空,跳过,进行下一个循环
if(newitem == null){
continue;
}
//赋值最新的价格
cartVO.setNewPrice(newitem.getPrice());
//赋值最新的库存
cartVO.setStock(newitem.getStock());
//赋值最新的状态
cartVO.setStatus(newitem.getStatus());
}
}
- 访问localhost:8088/carts 购物车服务后,观察购物车控制台,调用了哪一台商品服务器?

3.2 网关负载均衡 🍐
网关负载均衡

观察上图,思考网关接收到前端检索商品的请求后,道理访问的是下面的哪一台服务器?

关于网关最后要路由到哪个微服务,可以观察网关的配置

直接访问前端http://localhost:18080 登录后,检索商品,观察 1,2,3号服务器的日志输出请求!!

总结
课堂作业
- gateway访问微服务的负载均衡是使用什么策略?🎤