SpringCloud入门(1) Eureka Ribbon Nacos

这里写目录标题

认识微服务

单体架构 :将业务的所有功能集中在一个项目中开发,打成一个包部署。

优点:架构简单;部署成本低

缺点:耦合度高(维护困难、升级困难)

分布式架构 :根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发,称为一个服务

优点:降低服务耦合;有利于服务升级和拓展

缺点:服务调用关系错综复杂

分布式架构虽然降低了服务耦合,但是服务拆分时也有很多问题需要思考:

  • 服务拆分的粒度如何界定?
  • 服务之间如何调用?
  • 服务的调用关系如何管理?

需要制定一套行之有效的标准来约束分布式架构。微服务是一种经过良好架构设计的分布式架构方案,微服务的架构特征:

  • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责
  • 自治:团队独立、技术独立、数据独立,独立部署和交付
  • 面向服务:服务提供统一标准的接口,与语言和技术无关
  • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题

SpringCloud

SpringCloud是目前国内使用最广泛的微服务框架。官网地址

SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。

其中常见的组件包括:

另外,SpringCloud底层是依赖于SpringBoot的,并且有版本的兼容关系,如下。我们学习的版本是 Hoxton.SR10,因此对应的SpringBoot版本是2.3.x版本。

服务拆分和远程调用

任何分布式架构都离不开服务的拆分,微服务也是一样。

微服务拆分时的几个原则:

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

服务拆分案例

下面进行服务拆分示例,以课前资料中的微服务cloud-demo为例,其结构如下:

cloud-demo:父工程,管理依赖

  • order-service:订单微服务,负责订单相关业务
  • user-service:用户微服务,负责用户相关业务

要求:

  • 订单微服务和用户微服务都必须有各自的数据库,相互独立
  • 订单服务和用户服务都对外暴露Restful的接口
  • 订单服务如果需要查询用户信息,只能调用用户服务的Restful接口,不能查询用户数据库

首先,将课前资料提供的cloud-order.sqlcloud-user.sql导入到mysql中,分别使用两个database去存储这些表。可以在idea里连接好mysql后,然后创建两个database之后直接导入sql脚本:

cloud-user表中初始数据如下:

cloud-order表中初始数据如下:

cloud-order表中持有cloud-user表中的id字段。

用IDEA导入课前资料提供的Demo,项目结构如下:

导入后,会在IDEA右下角出现弹窗,点击弹窗,然后按下图选择:

会出现这样的菜单,会把项目中所有的微服务展示出来,也可以idea里alt+F8或者点击下面的services弹出窗口:

再配置项目使用的JDK即可,最后别忘记在两个项目的application.yml配置文件里修改mysql的用户名和密码。

实现远程调用 RestTemplate

在order-service服务中,有一个根据id查询订单的接口:

根据id查询订单,返回值是Order对象,如图:

其中的user为null。在user-service中有一个根据id查询用户的接口:

查询的结果如图:

案例需求:修改order-service中的根据id查询订单业务,要求在查询订单的同时,根据订单中包含的userId查询出用户信息,一起返回。

因此,我们需要在order-service中 向user-service发起一个http的请求,调用http://localhost:8081/user/{userId}这个接口。

大概的步骤是这样的:

  • 注册一个RestTemplate的实例到Spring容器,这个工具是spring提供的用于发送http请求的
  • 修改order-service服务中的OrderService类中的queryOrderById方法,根据Order对象中的userId查询User
  • 将查询的User填充到Order对象,一起返回

注册RestTemplate。首先,我们在order-service服务中的OrderApplication启动类中,注册RestTemplate实例:

java 复制代码
package cn.itcast.order;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

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

说明:以上在启动类中声明第三方Bean的作法,不建议使用(项目中要保证启动类的纯粹性

实现远程调用。修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法:

这时候再次根据id查询订单,返回值的Order对象包含了User信息,如图:

Eureka注册中心

假如我们的服务提供者user-service部署了多个实例,如图:

  • order-service在发起远程调用的时候,该如何得知user-service实例的ip地址和端口?
  • 有多个user-service实例地址,order-service调用时该如何选择?
  • order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?

这些问题都需要利用SpringCloud中的注册中心来解决,其中最广为人知的注册中心就是Eureka.

Eureka的结构和作用

问题1:order-service如何得知user-service实例地址?

  • user-service服务实例启动后,将自己的信息注册到eureka-server(Eureka服务端)。这个叫服务注册
  • eureka-server保存服务名称到服务实例地址列表的映射关系
  • order-service根据服务名称,拉取实例地址列表。这个叫服务发现或服务拉取

问题2:order-service如何从多个user-service实例中选择具体的实例?

  • order-service从实例列表中利用负载均衡算法选中一个实例地址
  • 向该实例地址发起远程调用

问题3:order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?

  • user-service会每隔一段时间(默认30秒)向eureka-server发起请求,报告自己状态,称为心跳
  • 当超过一定时间没有发送心跳时,eureka-server会认为微服务实例故障,将该实例从服务列表中剔除
  • order-service拉取服务时,就能将故障实例排除了

注意:一个微服务,既可以是服务提供者,又可以是服务消费者,因此eureka将服务注册、服务发现等功能统一封装到了eureka-client端

搭建eureka-server

注册中心服务端:eureka-server,这必须是一个独立的微服务.在cloud-demo父工程下,创建一个子模块

引入SpringCloud为eureka提供的starter依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

给eureka-server服务编写一个启动类,一定要添加一个@EnableEurekaServer注解,开启eureka的注册中心功能:

java 复制代码
package cn.itcast.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}

编写配置文件,application.yml文件内容如下:

yaml 复制代码
server:
  port: 10086
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url: 
      defaultZone: http://127.0.0.1:10086/eureka

启动微服务,然后在浏览器访问:http://127.0.0.1:10086,看到下面结果应该是成功了,可以看到有多少个实例被注册到Eureka,以及对应的服务名和ip端口

服务注册

将user-service注册到eureka-server中去。在user-service的pom文件中,引入下面的eureka-client依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

在user-service中,修改application.yml文件,添加服务名称、eureka地址:

yaml 复制代码
spring:
  application:
    name: userservice
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

为了演示一个服务有多个实例的场景,我们添加一个SpringBoot的启动配置,再启动一个user-service。首先,复制原来的user-service启动配置:

然后,在弹出的窗口中,填写信息,其中注意使用VM参数-Dserver.port=8082指定端口防止两个服务端口冲突。

现在,SpringBoot窗口会出现两个user-service启动配置,不过,第一个是8081端口,第二个是8082端口。

启动两个user-service实例,查看eureka-server管理页面:

服务发现

将order-service的逻辑修改:向eureka-server拉取user-service的信息,实现服务发现。首先在order-service的pom文件中,引入下面的eureka-client依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

服务发现也需要知道eureka地址,因此也要配置eureka信息,在order-service中,修改application.yml文件,添加服务名称、eureka地址:

yaml 复制代码
spring:
  application:
    name: orderservice
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

最后,我们要去eureka-server中拉取user-service服务的实例列表,并且实现负载均衡。不过这些动作不用我们去做,只需要添加一些注解即可。

在order-service的OrderApplication中,给RestTemplate这个Bean添加一个@LoadBalanced注解:

修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法。修改访问的url路径,用服务名代替ip、端口,这里的服务名写成userservice是因为我们去Erueka里查看实例列表时user-service服务的名字就是userservice,服务名字是我们在application.yml指定好的。

spring会自动帮助我们从eureka-server端,根据userservice这个服务名称,获取实例列表,而后完成负载均衡。

Ribbon负载均衡 @LoadBalanced

上一节中服务发现中,我们添加了@LoadBalanced注解,即可实现负载均衡功能,这是什么原理呢?我们发出的请求明明是http://userservice/user/1,怎么变成了http://localhost:8081的呢?SpringCloud底层其实是利用了一个名为**Ribbon**的组件,来实现负载均衡功能的。

LoadBalancerInterceptor帮我们根据service名称,获取到了服务实例的ip和端口。这个类会在对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id。

LoadBalancerIntercepor源码解析

我们debug进行源码跟踪,在LoadBalancerIntercepor打上断点。

LoadBalancerIntercepor,实现了ClientHttpRequestInterceptor接口(客户端请求拦截)。

可以看到这里的intercept方法,拦截到了用户的HttpRequest请求,然后做了几件事:

  • request.getURI():获取请求uri,本例中就是 http://user-service/user/8
  • originalUri.getHost():获取uri路径的主机名,其实就是服务id,user-service
  • this.loadBalancer.execute():处理服务id,和用户请求。

这里的this.loadBalancerLoadBalancerClient类型,我们继续跟入execute方法:

代码是这样的:

  • getLoadBalancer(serviceId):根据服务id获取ILoadBalancer,而ILoadBalancer会拿着服务id去eureka中获取服务列表并保存起来。
  • getServer(loadBalancer):利用内置的负载均衡算法,从服务列表中选择一个。本例中,可以看到获取了8082端口的服务

放行后,再次访问并跟踪,发现获取的是8081:

果然实现了负载均衡。在刚才的代码中,可以看到获取服务使通过一个getServer方法来做负载均衡,我们继续跟入:

继续跟踪源码chooseServer方法,发现这么一段代码:

我们看看这个rule是谁:

这里的rule默认值是一个RoundRobinRule,看类的介绍:

这不就是轮询的意思嘛。

到这里,整个负载均衡的流程我们就清楚了。

总结

SpringCloudRibbon的底层采用了一个拦截器,拦截了RestTemplate发出的请求,对地址做了修改。用一幅图来总结一下:

基本流程如下:

  • 拦截我们的RestTemplate请求http://userservice/user/1
  • RibbonLoadBalancerClient会从请求url中获取服务名称,也就是userservice
  • DynamicServerListLoadBalancer根据userservice到eureka拉取服务列表
  • eureka返回列表,localhost:8081、localhost:8082
  • IRule利用内置负载均衡规则,从列表中选择一个,例如localhost:8081
  • RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求

负载均衡策略

负载均衡的规则都定义在IRule接口中,而IRule有很多不同的实现类:

不同规则的含义如下:

内置负载均衡规则类 规则描述
RoundRobinRule 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。
AvailabilityFilteringRule 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为"短路"状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的..ActiveConnectionsLimit属性进行配置。
WeightedResponseTimeRule 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
ZoneAvoidanceRule 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。
BestAvailableRule 忽略那些短路的服务器,并选择并发数较低的服务器。
RandomRule 随机选择一个可用的服务器。
RetryRule 重试机制的选择逻辑

默认的实现就是ZoneAvoidanceRule,是一种轮询方案,从上图可以看到它继承了ClientConfigEnableRoundRobinRule,所以也是采用的轮询规则。

那如何选择使用哪种轮询呢?通过定义IRule实现可以修改负载均衡规则,有两种方式:

  1. 代码方式:在order-service中的OrderApplication类中,定义一个新的IRule注入到IOC容器,这样order-service在调用服务时使用的就是RandomRule负载均衡。
java 复制代码
@Bean
public IRule randomRule(){
    return new RandomRule();
}

这种方式作用的是全局,即在order-service里调用的所有微服务都是采用这个负载均衡。如果想指定服务名称的负载均衡规则可以采用第二种方法:

  1. 配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:
yaml 复制代码
userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则 

注意,一般用默认的负载均衡规则,不做修改。

饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。

而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面在order-service的配置开启饥饿加载

yaml 复制代码
ribbon:
  eager-load:
    enabled: true # 开启饥饿加载
    clients: userservice # 指定饥饿加载的服务名称

如果想指定多个饥饿加载服务应该这样写:

yaml 复制代码
clients:
  - userservice

Nacos注册中心

Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。

安装与使用Nacos

在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
GitHub主页
GitHub的Release下载页

windows解压到任意非中文目录下,在bin目录执行命令startup.cmd -m standalone即可,我们这里是学习,先用单机模式启动。

浏览器输入地址:http://127.0.0.1:8848/nacos即可,默认的账号密码都是nacos

配置文件可以进入conf文件夹的application.properties修改,默认占用8848端口,如果被占用也可进入修改。

服务注册到nacos

Nacos是SpringCloudAlibaba的组件,而SpringCloudAlibaba也遵循SpringCloud中定义的服务注册、服务发现规范。因此使用Nacos和使用Eureka对于微服务来说,并没有太大区别。

主要差异在于:

  • 依赖不同
  • 服务地址不同

引入依赖

在cloud-demo父工程的pom文件中的<dependencyManagement>中引入SpringCloudAlibaba的依赖:

xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.2.6.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

然后在user-service和order-service中的pom文件中引入nacos-discovery依赖:

xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

注意:不要忘了注释掉eureka的依赖。

配置nacos地址

在user-service和order-service的application.yml中添加nacos地址:

yaml 复制代码
server:
  port: 8081
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false
    username: root
    password: 1234
    driver-class-name: com.mysql.jdbc.Driver
# 这里添加nacos地址
  cloud:
    nacos:
      server-addr: localhost:8848
  application:
    name: userservice
#eureka:
#  client:
#    service-url:
#      defaultZone: http://127.0.0.1:10086/eureka
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的地址

重启微服务后,登录nacos管理页面,可以看到微服务信息:

服务分级存储模型

一个服务 可以有多个实例,例如我们的user-service,可以有:

  • 127.0.0.1:8081
  • 127.0.0.1:8082
  • 127.0.0.1:8083

假如这些实例分布于全国各地的不同机房,例如:

  • 127.0.0.1:8081,在上海机房
  • 127.0.0.1:8082,在上海机房
  • 127.0.0.1:8083,在杭州机房

Nacos就将同一机房内的实例 划分为一个集群

也就是说,user-service是服务,一个服务可以包含多个集群,如杭州、上海,每个集群下可以有多个实例,形成分级模型。

微服务互相访问时,应该尽可能访问同集群实例,因为本地访问速度更快。当本集群内不可用时,才访问其它集群。

杭州机房内的order-service应该优先访问同机房的user-service。

给user-service配置集群 修改user-service的application.yml文件,添加集群配置:

yaml 复制代码
spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ # 集群名称

重启两个user-service实例后,我们可以在nacos控制台看到下面结果:

我们再次复制一个user-service启动配置,添加属性:

sh 复制代码
-Dserver.port=8083 -Dspring.cloud.nacos.discovery.cluster-name=SH

配置如图所示:

启动UserApplication3后再次查看nacos控制台:

这里其实也可以在启动完两个HZ集群之后,修改user-service的application.yml文件,将集群名称改为SH,然后启动UserApplication3即可。

同集群优先的负载均衡

默认的ZoneAvoidanceRule并不能实现根据同集群优先来实现负载均衡。

因此Nacos中提供了一个NacosRule的实现,可以优先从同集群中挑选实例。

1)给order-service配置集群信息

修改order-service的application.yml文件,添加集群配置:

sh 复制代码
spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ # 集群名称

2)修改负载均衡规则

修改order-service的application.yml文件,添加负载均衡规则:

yaml 复制代码
userservice:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则 

# 注意之前的饥饿加载配置还在 不要覆盖到这个上面了
ribbon:
  eager-load:
    enabled: true
    clients:
      - userservice

重启order服务,进行查询,观察控制台可以发现,order服务只会调用同集群下的user服务。

权重配置

实际部署中会出现这样的场景:服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。

但默认情况下NacosRule是同集群内随机挑选,不会考虑机器的性能问题。因此,Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。

在nacos控制台,找到user-service的实例列表,点击编辑,即可修改权重:

在弹出的编辑窗口,修改权重:

注意:如果权重修改为0,则该实例永远不会被访问

环境隔离

Nacos提供了namespace来实现环境隔离功能。

  • nacos中可以有多个namespace
  • namespace下可以有group、service等
  • 不同namespace之间相互隔离,例如不同namespace的服务互相不可见

默认情况下,所有service、data、group都在同一个namespace,名为public:

我们可以点击页面新增按钮,添加一个namespace:

然后,填写表单:

就能在页面看到一个新的namespace:

给微服务配置namespace只能通过修改配置来实现。

例如,修改order-service的application.yml文件:

yaml 复制代码
spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ
        namespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间,填ID

重启order-service后,访问控制台,可以看到下面的结果:

此时访问order-service,因为namespace不同,会导致找不到userservice,控制台会报错:

Nacos与Eureka的区别

Nacos的服务实例分为两种类型:

  • 临时实例:如果实例宕机超过一定时间,会从服务列表剔除,默认的类型。

  • 非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例。

配置一个服务实例为永久实例:

yaml 复制代码
spring:
  cloud:
    nacos:
      discovery:
        ephemeral: false # 设置为非临时实例

Nacos和Eureka整体结构类似,服务注册、服务拉取、心跳等待,但是也存在一些差异:

  • Nacos与eureka的共同点

    • 都支持服务注册和服务拉取
    • 都支持服务提供者心跳方式做健康检测
  • Nacos与Eureka的区别

    • Nacos支持主动服务检测提供者状态,临时实例采用心跳模式,非临时实例采用主动检测模式
    • 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
    • Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
    • Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式(集群的概念后期会学到)
相关推荐
一个有温度的技术博主3 小时前
Spring Cloud 入门与实战:从架构拆分到核心组件详解
spring·spring cloud·架构
uNke DEPH4 小时前
SpringCloud Gateway 集成 Sentinel 详解 及实现动态监听Nacos规则配置实时更新流控规则
spring cloud·gateway·sentinel
慕容卡卡8 小时前
你所不知道的RAG那些事
java·开发语言·人工智能·spring boot·spring cloud
dLYG DUMS9 小时前
Spring Cloud Data Flow 简介
后端·spring·spring cloud
Ken_111520 小时前
SpringCloud系列(61)--Nacos之服务配置中心的介绍与使用
spring cloud
Ken_111521 小时前
SpringCloud系列(62)--Nacos之命名空间、分组和DataID三者之间的关系
spring cloud
Ken_11151 天前
SpringCloud系列(63)--Nacos读取不同配置之DataID配置方案
spring cloud
Devin~Y1 天前
从Spring Boot到Spring AI:音视频AIGC内容社区Java大厂面试三轮连环问(含Kafka/Redis/安全/可观测性答案)
java·spring boot·redis·spring cloud·kafka·spring security·resilience4j
qqty12171 天前
springcloud springboot nacos版本对应
spring boot·spring·spring cloud
一个有温度的技术博主1 天前
微服务技术选型:Dubbo、Spring Cloud与Spring Cloud Alibaba深度对比
spring cloud·微服务·dubbo