目录
2. SpringBoot 整合 Nacos 实现服务注册中心
1. 什么是注册中心?
注册中心是微服务架构中的一个重要组件,它用于实现服务注册与服务发现。
【思考一】什么叫服务注册 ? 什么叫服务发现 ?
- 服务注册就是生产者,它是服务的提供方,它用于将服务存储起来;
- 服务发现是注册中心将服务列表推送给调用服务的消费者 / 消费者向注册中心拉取服务列表;
Nacos 结合了两者的优势,提供了一个更加灵活和高效的服务发现机制。在默认情况下,Nacos 使用推模式来通知消费者,但消费者仍然会定期拉取服务列表,以应对可能出现的消息丢失或延迟等问题。
【思考二】为什么需要服务注册和服务发现呢 ?服务之间直接调用存在什么问题呢 ?
① 服务注册与服务发现是为了更好的实现服务与服务之间的通信,降低服务与服务间的耦合度。(健康检测机制)。
② 服务之间直接调用存在的问题:
- 每个服务都可能有很多份,那我在调用的时候,就需要写很多份,而且需要一个一个手动分配,工作量太大;
- 微服务里边,服务起码得是集群B1,B2,B3......,如果直接写死,当某一天,某些服务挂了,就会引发一系列问题,从而就无法保证系统的高可用。
具体有以下几个问题:
单点故障:因为调用方直接依赖于特定的服务实例,所以当这个实例不可用时,调用方无法找到其他的可用实例,从而导致整个调用链路中断。
延迟与超时:当一个服务不可用,调用方可能会因为网络超时而等待很长时间,这会导致用户体验下降,并可能引发其他的连锁故障。
无法进行负载均衡:如果多个实例都可用,但调用方总是直接调用某一个特定实例,那么这将导致流量分布不均,某些实例可能会过载,而其他实例则处于空闲状态。
无法进行故障转移:当目标服务实例出现故障时,调用方无法自动切换到其他健康的实例。
扩展性受限:如果服务需要进行扩展,增加新的实例,调用方可能需要修改代码或配置来适应新的服务地址,这增加了运维的复杂性。
不透明的依赖关系:如果所有的服务都是直接相互调用,那么很难快速地了解服务之间的依赖关系,这在故障排查时可能会增加很多麻烦。
基于以上问题,所以我们需要使用服务注册于服务发现来更好的实现服务与服务之间的通信!
1.2 注册中心的作用
- 服务注册:服务实例启动时,将自身注册到注册中心,包括服务名,地址,端口等;
- 服务发现:消费者向注册中心拉取服务列表 / 注册中心推送服务列表给消费者;
- 服务健康检测:注册中心会定期检测服务实例的健康状态,并过滤不健康的实例;
- 服务路由:决定如何将请求分发到合适的服务实例,通常涉及到负载均衡策略;
- 服务监控:监控服务的状态,如响应时间、错误率等,以便及时发现并处理问题;
- 服务更新:当服务实例信息变更时,向注册中心发送更新信息通知。
2. SpringBoot 整合 Nacos 实现服务注册中心
2.1 将服务注册到 Nacos
实现之前,Nacos 服务要启动起来,创建好 SpringBoot 多模块项目(一个父模块和两个子模块)
如果不会创建 SpringBoot 多模块项目的,可以去看我的这篇文章:https://blog.csdn.net/xaiobit_hl/article/details/134144828
【实现步骤】
- 添加 Nacos discovery 支持
- 配置 Nacos 连接信息
- 编写代码(开发接口)
PS:完成以上三步后,当前项目就会自动注册到 Nacos 中!
② Nacos 连接信息
spring:
application:
name: nacos-discovery-demo
# Nacos 服务名 (命名不要使用下划线 _,早期的SpringCloud版本不支持)
cloud:
nacos:
discovery:
server-addr: localhost:8848 # 连哪个 Nacos 的注册中心
username: nacos
password: nacos
# ephemeral: false # false 设置此服务为永久实例,true 为临时实例
server:
port: 0 # 动态端口号
③ 实现接口
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private ServletWebServerApplicationContext context; // 用于获取动态端口号
// 服务
@RequestMapping("/getnamebyid")
public String getNameById(Integer id) {
return "provider-name-" + id +
" | port:" + context.getWebServer().getPort();
}
}
PS:接口的实现不重要,重要的是我们能把它注册到注册中心,然后能通过另一个项目,调用到它!
【思考】为什么服务注册到注册中心,需要使用动态端口? 而不使用固定端口?
只有当用户去使用 Nacos 的时候,才需要使用一个固定的端口号,它不能每次变化,每次变的话,用户就猜不出来这个端口号是多少。
而对于微服务这边,它只需要将服务注册到注册中心,供消费者来使用,具体端口号是多少,对于程序来说,它不必预先知道。
并且使用动态端口有以下好处:
灵活性:动态端口允许服务在任何可用的端口上启动,避免了手动配置端口或处理端口冲突的问题。
高可用性:如果某个端口不可用(被其他进程占用或由于某些原因被阻止),服务可以选择其他任何可用的端口来启动,这增加了服务的高可用性。
简化配置:不需要为每个服务的实例手动分配和管理固定的端口号。
支持多实例:动态端口允许在同一台机器上运行多个相同的服务实例,这对于快速扩展和负载均衡很有利。
【测试服务注册中心】
当我们将服务注册到注册中心后,就可以在 Nacos 控制台看到这些信息了:
这个时候,我们就可以测试一下微服务里边的接口好不好使,点击操作下面的详情:
我们拿着 IP + 动态端口去访问接口,是可以正常访问得到的:
【上线下线 Nacos 服务的作用】
服务的上线与下线通常是指服务实例的动态注册与注销,利用这一特性,可以实现以下几种发布策略:
灰度发布:灰度发布是指先让部分用户试用新版本,而其它用户还在使用旧版本。通过Nacos,可以将新版本的服务实例注册到Nacos中,然后通过权重配置,让部分用户路由到新的服务实例上。(也可以通过标签配合 geteway 网关规则)
蓝绿部署:蓝绿部署是一种将新旧版本同时部署的策略,通过路由控制用户流量到不同的版本。通过Nacos的服务注册与发现,可以轻松地实现蓝绿部署:新版本(绿色)可以注册到Nacos,当确认新版本稳定后,可以将流量切换到新版本,同时下线旧版本(蓝色)。
金丝雀发布:金丝雀发布是灰度发布的一种,它先将新版本发布给一小部分用户,然后根据这部分用户的反馈和系统的表现,决定是否将新版本推广给更多的用户。利用Nacos的权重配置,可以轻松地实现金丝雀发布。
紧急回滚:如果新版本出现问题,就可以快速地在Nacos中下线新版本的服务实例,同时上线旧版本的服务实例,以此来实现紧急回滚。
PS:当服务被下线时,注册中心在公布服务列表的时候,就不会包含已经下线的服务了,即使它是健康状态。
【点击上线或下线报错问题】
当点击上线下线如果报错了,或者创建临时实例 / 永久实例,报错了,可以通过以下方法来解决:
① 停止 Nacos 服务 (Ctrl C 多按几遍)
② 删除 nacos/data 目录下的 protocol 文件夹 (非常好使)
③ 开启 Nacos 服务,再次点击上下线就不会报错了
2.2 实现消费者
【前置工作】
1. 创建一个消费者的 module,在 pom.xml 文件中声明父节点
<parent>
<groupId>com.example</groupId>
<artifactId>nacos-discovery-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
2. 删除不必要的依赖:test,nacos-discovery,java.version 等声明,以及 dependencyManagement 依赖。
3. 在父模块中声明子模块
<modules>
<module>provider</module>
<module>consumer</module>
</modules>
【实现步骤】
- 添加框架支持 【nacos discovery、spring cloud LoadBalancer、spring cloud OpenFeign】
- 配置 Nacos 连接信息
- 开启 OpenFeign 功能
- 声明 OpenFeign 式的 Service 【生产者的的服务】
- 调用服务【调用生产者的服务】
② 配置 Nacos 连信息
spring:
application:
name: nacos-consumer-demo
cloud:
nacos:
discovery:
server-addr: localhost:8848
username: nacos
password: nacos
register-enabled: false #消费者(不需要将此服务注册到 nacos)
server:
port: 8080 # 使用固定端口
③ 开启 OpenFeign 功能
@SpringBootApplication
@EnableFeignClients // 开启 OpenFeign
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
PS:在启动类上加上 @EnableFeignClients 注解!
④ 声明 OpenFeign 式的 Service
@Service
@FeignClient("nacos-discovery-demo") //调用nacos中的nacos-discovery-demo服务
public interface UserService {
@RequestMapping("/user/getnamebyid") // 调用生产者的接口
public String getNameById(@RequestParam("id") Integer id); //@RequestParam
}
方法中的参数最好添加 @RequestParam 注解,否则可能拿不到参数。
⑤ 调用服务
@RestController // 消费者
public class BusinessController {
@Autowired
private UserService userService;
@RequestMapping("/getnamebyid")
public String getNameById(Integer id) {
return userService.getNameById(id);
}
}
【测试消费者】
开启两个生产者和一个消费者(测试负载均衡默认的轮询方式)
根据配置信息,使用 localhost:8080/getnamebyid?id=2 进行访问>>
第一次访问:
刷新后:(轮询的方式)
3. 服务列表各个参数的含义、作用以及应用场景
1. 服务名:服务的唯一标识符,用于区分不同的服务,对应我们配置文件写的 spring.application.name.
2. 分组名称:服务的分组标识,用于将服务进行逻辑隔离的。我们可以根据不同的环境或用途为服务设置不同的分组,例如“测试组”、“开发组”、“生产组”等。这样的隔离机制有助于管理和维护服务。
3. 集群数目:在一个服务内,可以有多个集群,每个集群下有多个服务实例。
4. 实例数:当前服务注册到Nacos的总实例数量,包括健康和不健康的实例。
5. 健康实力数:可以正常提供服务,没有故障的实例。
6. 触发保护阈值:它是一个 0-1 之间的数,表示当健康实例数低于此阈值时,Nacos会阻止所有服务实例的自动注销,以保护剩余的健康实例。(可以在服务详情里面的编辑服务中进行设置)
为什么要有保护阈值 ?
它是为了防止服务雪崩的。比如说服务集群里边原本有 1000 个实例,但是现在有 999 个实例都挂了,只剩下一个实例了,那么原本 1000 个人干的活,现在就只剩一个人了,如果我们再把所有的活再派给这一个人来干,那么这个人他肯定也不在了,他肯定离职了。对于咱们系统来说也是一样,如果集群实例数太少的话,这时候还把所有的流量分发过去,那就会造成服务瘫痪,进而造成上游调用这个服务的整体瘫痪,进而造成服务雪崩。所以需要保护阈值。
保护阈值它是如何防止服务雪崩的 ?
想要解决服务雪崩,无非就是加锁排队,但是 Nacos 它本身又不做限流,只有注册中心和配置中心,那它是怎么做到既没有限流功能,又要保证不会发生雪崩问题的呢?它的做法是 "躺平",当保护阈值为 true 的时候,它会将所有的请求分发给所有的服务实例(不管健康与否),即使有些服务实例已经挂掉了,以此来保护所剩无几的健康实例!
【保护阈值演示案例】
① 准备两个永久服务实例,一个消费者,然后停掉一个服务
如何快速创建相同实例 >>
PS:永久实例 - 对应配置文件中的 ephemeral: false
② 将保护阈值设置为 0.5,这时保护阈值就变为 true 了
③ 使用消费者调用服务
例如:localhost:8080/getnamebyid?id=2
当我们更改了权重,阈值等参数,它默认是会有缓存的,感知不到,所以需要重启消费者才能看到 nacos 的防止雪崩的策略。
重启消费者后 >>
第一次访问:
第二次访问:
PS:第三次访问和第一次访问一样,第四次访问又报错,这就是 Nacos 的一个解决服务雪崩的手段!
【服务详情中的一些参数的含义】
1. 元数据:与服务相关的额外信息,可以是键值对形式的任意数据。(它会自动设置进去)
2. 服务路由类型:用于指定如何在消费者和成产者之间进行路由决策的,它可以实现请求的负载均衡。
PS:服务路由类型最主要的作用就是实现 CMDB,地域就近访问的,什么叫地域就近访问 ?
当有北京的调用者来获取服务的时候,他肯定是调用北京的服务是最快的,因为对于北京的调用者来说,北京的网络,路由调的次数肯定比深圳少,所以它的速度肯定快,那么深圳的调用者肯定是调用深圳的服务最快,这就是地域就近访问。
3. 权重:实例级别的设置。范围为 0-10000,用于负载均衡决策,权重越大,分配给该实例的流量越大。当权重为 0 的时候,和点击下线功能是一样的效果。
Nacos 的负载均衡策略总的来说有两种方式:
① 基于健康检测和权重
② 基于第三方 CMDB 的标签负载均衡器
这在官方的 《Nacos架构与原理》一书中原话是这样说的:
在 Nacos 0.7.0 版本中,我们除了提供基于健康检查和权重的负载均衡方式外,
还新提供了基于第三方 CMDB 的标签负载均衡器
🍁临时实例 VS 永久实例
① 定义和删除方式不同
1. 永久实例:其注册信息会一直保留在 Nacos 服务器上,直到主动注销或被删除。这就意味着即使服务实例下线,或者不健康了,他的注册信息仍然会保留在 Nacos 上。
2. 临时实例:其注册信息在服务实例下线,断开连接或者不健康时,会自动从服务注册列表中被删除。
如何删除永久实例:1.停服务;2. 删除 nacos/data 目录下的 protocol 文件夹;3. 启动 nacos 服务
② 健康检测机制不同
1. 永久实例:服务器反向探测机制,服务器主动来询问永久实例的健康与否。
2. 临时实例:客户端主动上报机制,客户端主动向服务器汇报自己的健康与否。
PS:临时实例每隔 5 秒就会主动上报一次自己的健康状态,发送的数据包叫做心跳包,发送心跳包的机制叫做心跳机制。如果 Nacos 服务端在 15 秒都没收到心跳,就会将实例设置为不健康,在 30 秒没收到心跳时就会将这个临时实例摘除。