02_微服务之Rest远程调用,Eureka注册中心以及Ribbon负载均衡

文章目录

  • 1. 微服务拆分与远程调用
    • 1.1 微服务拆分时的几个原则:
    • 1.2 远程调用
      • 1.2.1 RestTemplate
      • 1.2.2 服务的提供者与消费者
    • 1.3 Eureka注册中心
      • 1.3.1 什么是Eureka
      • 1.3.2 Eureka使用姿势
        • 1.3.2.1 搭建eureka-server注册中心
        • 1.3.2.2 服务注册
        • 1.3.2.3 服务拉取(发现)
      • 1.3.3 Eureka使用总结
  • 2. Ribbon负载均衡
    • 2.1 负载均衡原理
      • 2.1.1 源码跟踪
        • 2.1.1.1 LoadBalancerInterceptor
        • 2.1.1.2 LoadBalancerClient
        • 2.1.1.3 负载均衡策略实现:IRule接口
      • 2.1.2 总结
    • 2.2 负载均衡策略:IRule
      • 2.2.1 内置规则
      • 2.2.2 修改默认规则:
    • 2.3 饥饿加载

1. 微服务拆分与远程调用

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

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

可以设置一个父工程统一管理依赖与版本

子工程继承父工程,导入依赖,不写代码

孙子工程继承子工程, 根据业务分出多个孙子工程

1.2 远程调用

Q : 我们在微服务中数据独立, 每个服务对应一个自己的数据库, 那当我们在某个服务中需要其他服务的数据库中的数据, 或者其他的需要调用其他服务时怎么办?

A : 这就是服务之间的远程调用问题

1.2.1 RestTemplate

为了解决远程调用问题, spring提供了RestTemplate可以在Java代码中发起http请求:

  1. 首先, 我们需要注册RestTemplate示例(Bean),由于bean的注入只能放在配置类里,而启动类就是一个配置类, 因此可以将RestTemplate放在配置类或者启动类中, 这里以放在启动类为例:
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.ahua.order.mapper")
@SpringBootApplication
public class Application {

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

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
  1. 在service层中(或者mapper?)发起请求调用其他服务即可:

    RestTemplate提供了一系列的ForObject(url, class)方法, 例如get请求就是GetForObject, Post就是PostForObject

    其中参数一url是请求url(从http开始写起), 第二个参数为返回值类型, 例如User.class

    示例:

// 请求url地址:
String url = "http://localhost:8080/user/...";
// 发起远程调用:
User user = restTemplate.GetForObject(url, User.class);

注意:

​ 这种方法存在了硬编码问题,因此需要eureka作为注册中心, 后续补充eureka

1.2.2 服务的提供者与消费者

在服务调用关系中,会有两个不同的角色:

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

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

但是,服务提供者与服务消费者的角色并不是绝对的,而是相对于业务而言

如果服务A调用了服务B,而服务B又调用了服务C,服务B的角色是什么?

  • 对于A调用B的业务而言:A是服务消费者,B是服务提供者
  • 对于B调用C的业务而言:B是服务消费者,C是服务提供者

因此,服务B既可以是服务提供者,也可以是服务消费者

1.3 Eureka注册中心

1.3.1 什么是Eureka

在上面的远程调用中, url是被写死的, 但实际情况下怎么准确得知另一个服务的ip地址与端口呢?

当服务提供者有多个示例地址时, 又要如何选择呢?

消费者又如何知道服务提供者是否依然健康,是不是已经宕机?

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

Eureka:

​ 注册中心(为微服务注册), 其中EurekaServer是服务端,所有的微服务都是客户端, 因为一个微服务,既可以是服务提供者,又可以是服务消费者,因此eureka将服务注册、服务发现等功能统一封装到了eureka-client端

​ 每个服务启动时都会向Eureka注册自己的服务信息(服务注册),eureka-server保存服务名称到服务实例地址列表的映射关系, 此时服务远程调用只需要向Eureka拉取对应的服务信息即可(服务拉取或发现 : 根据服务名称拉取实例地址列表)

​ 如果拉取到了多个服务就使用负载均衡算法挑一个实例地址使用

​ 同时拉取到的这个服务正常情况挂(极端情况有可能),因为每个服务每隔三十秒都会向Eureka发送自己还活着的信息,报告自己状态, 称为心脏,相当于心脏每三十秒跳一次发给大脑(Eureka)告诉他我还活着,如果三十秒之后还没有发消息Eureka就认为他挂了,把他从注册列表中剔除)

1.3.2 Eureka使用姿势

1.3.2.1 搭建eureka-server注册中心

创建eureka服务首先需要注册中心服务端:eureka-server,这必须是一个独立的微服务

  1. 在父工程下创建子模块eureka-server, 并引入SpringCloud为eureka提供的starter依赖:
<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
  1. 编写eureka-server的启动类, 注意:

    一定要添加一个==@EnableEurekaServer注解,开启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);
    }
}
  1. 由于eureka_server也是一个微服务,因此也需要配置服务地址与服务名称(配置yml),这样子eureka才可以把自己也注册到注册中心中,这样子如果出现多个eureka服务时,eureka集群之间才可以互相做数据的交互,也就是说做服务注册时就是在yml中配置服务地址与名称
server:
  port: 10086
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url: 
      defaultZone: http://127.0.0.1:10086/eureka
  1. 启动微服务,然后在浏览器访问:http://127.0.0.1:10086

    看到Eureka页面就成功了

(同时在父工程有一个boot的依赖库)

1.3.2.2 服务注册

  1. 在其他服务的pom文件中,引入下面的eureka-client依赖

    与上面的依赖名只有server与client的区别

<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
  1. 在需要注册的服务中, 修改application.yml文件,添加服务名称、eureka地址(注意是eureka的地址而不是自己的)

    也就是说,不管是eureka自己注册还是其他服务注册,都是配自己的服务名称以及eureka的服务地址(相对来说, Eureka自己配置时就是配置自己的地址, 而其他服务配置的是Eureka配置的地址):

spring:
  application:
    name: userservice
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

启动服务, 查看eureka-server管理页面出现服务地址与名称即配置成功

1.3.2.3 服务拉取(发现)

  1. 首先需要完成自己的服务注册(eureka-client依赖与yml配置)

  2. 接着我们要去eureka-server中拉取服务提供者的实例列表,并且实现负载均衡

    不过这些动作不用我们去做,只需要添加一些注解以及修改远程调用的路径:

    在RestTemplate这个Bean添加一个@LoadBalanced注解(用于实现负载均衡):

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

修改消费者在Service层中远程调用的访问url路径, 将ip地址与端口修改为服务名称, 示例:

String url = "http://userservice/user/...";

最后当我们运行并访问消费者资源时spring会自动帮助我们从eureka-server端,根据userservice这个服务名称,获取实例列表,而后完成负载均衡

1.3.3 Eureka使用总结

三步走:

  1. 创建Eureka注册中心服务

    1)导入eurek-server依赖

    2)配置Eureka服务名称与服务地址,先将自己注册了

  2. 服务注册

    1)导入eurek-client依赖

    2)配置自己的服务名称与eureka的服务地址

  3. 服务拉取

    1)在RestTemplate这个Bean添加一个@LoadBalanced注解

    2)修改远程调用url,用服务名代替ip、端口

2. Ribbon负载均衡

2.1 负载均衡原理

Q : 添加了@LoadBalanced注解,即可实现负载均衡功能,这是什么原理呢?

SpringCloud底层其实是利用了一个名为Ribbon的组件,来实现负载均衡功能的, 而这个@LoadBalanced注解的作用就是让程序走Ribbon组件

Q : 以及我们发出的请求明明是http://userservice/user/1,怎么变成了http://localhost:8081的呢?

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

2.1.1 源码跟踪

2.1.1.1 LoadBalancerInterceptor

02_微服务之Rest远程调用,Eureka注册中心以及Ribbon负载均衡_第1张图片

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

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

这里的this.loadBalancerLoadBalancerClient类型,可以继续跟入源码:

2.1.1.2 LoadBalancerClient

跟入LoadBalancerClient的execute方法:

02_微服务之Rest远程调用,Eureka注册中心以及Ribbon负载均衡_第2张图片

代码是这样的:

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

2.1.1.3 负载均衡策略实现:IRule接口

可以看到, 在execute方法中获取服务时通过一个getServer方法来做负载均衡:

跟入getServer()方法发现使用了RibbonLoadBalancerClient类中的chooseServer方法

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

02_微服务之Rest远程调用,Eureka注册中心以及Ribbon负载均衡_第3张图片

我们看看这个rule是谁:

02_微服务之Rest远程调用,Eureka注册中心以及Ribbon负载均衡_第4张图片

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

02_微服务之Rest远程调用,Eureka注册中心以及Ribbon负载均衡_第5张图片

这不就是轮询的意思嘛

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

2.1.2 总结

SpringCloudRibbon的底层采用了一个拦截器,拦截了RestTemplate发出的请求,从而实现了对地址的修改(去Eureka根据服务名拉取服务地址)以及负载均衡。用一幅图来总结一下:

02_微服务之Rest远程调用,Eureka注册中心以及Ribbon负载均衡_第6张图片

基本流程如下:

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

2.2 负载均衡策略:IRule

2.2.1 内置规则

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

02_微服务之Rest远程调用,Eureka注册中心以及Ribbon负载均衡_第7张图片

其中默认的是ZoneAvoidanceRule, 这个实现类是先根据Zone对服务器进行分类再进行轮循, 但由于机房一般都在同一个地方, 因此基本上理解为轮循即可

常用的内置规则类:

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

2.2.2 修改默认规则:

通过定义IRule实现可以修改负载均衡规则,有两种方式:

  1. 代码方式:在服务消费者中的Application启动类或者配置中,定义一个新的IRule

    需要什么规则在这个bean中返回什么规则即可

    这样子配置的是这个服务的全局规则

@Bean
public IRule randomRule(){
    return new RandomRule();
}
  1. 配置文件方式:在消费者的application.yml文件中,添加配置也可以修改规则

    需要给哪个服务提供者配置策略就写哪个服务提供者的服务名称

    此时配置的是这个消费者针对这个提供者的策略

userservice: # 给某个微服务提供者配置负载均衡规则,这里是userservice服务
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则 

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

2.3 饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,此时需要去完成服务拉取等, 因此请求时间会很长

而第一次加载之后后面就不需要加载了, 因为会被缓存到内存中, 因此后面的访问速度就会变快了

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

只需要将enabled设置为true, 同时需要指定饥饿加载的服务名称(也是消费者配置提供者)

ribbon:
  eager-load:
    enabled: true # 开启饥饿加载
    clients: userservice # 配置对哪个服务进行饥饿加载

配置对多个服务进行饥饿加载:

ribbon:
  eager-load:
    enabled: true # 开启饥饿加载
    # 指定需要饥饿加载的服务名称:
    clients: 
     - userservice
     - service_acl

你可能感兴趣的:(微服务/分布式,微服务,eureka,ribbon)