netflix ribbon 负载均衡 与 RestTemplate 概述

目录

RestTemplate Rest 模板

微服务之间 http 请求

eurekaserver_changsha

eurekaclient_food

eurekaclient_cat

访问测试

netflix ribbon 负载均衡

ribbon 基本使用


RestTemplate Rest 模板

1、在正式使用 Ribbon 之前,先了解一下 RestTemplate。本文环境:Java jdk 8 + Spring boot 2.1.3 + spring cloud Greenwich.SR1 + spring 5.1.5。

2、org.springframework.web.client.RestTemplate 类是 spring-web 模块中进行 HTTP 访问的 REST 客户端核心类。

3、通俗的说:以前在某个 Java 应用后台代码中如果想要向另外一个 Java 应用发起 Http 请求,则通常使用 Apache 的 HttpClient 库来做。而 spring cloud 微服务架构中,各个微服务之间会频繁的发起 http 请求进行通信,所以 spring 对 http 请求进行了封装,以便请求使用起来更加的方便。RestTemplate 对 http 的封装类似 JdbcTemplate 对 jdbc 的封装。

4、RestTemplate 类提供了 3 个构造函数:

1)RestTemplate()  //默认使用 org.springframework.http.client.SimpleClientHttpRequestFactory 客户端,底层使用 jdk 标准的 API,即 java.net 包下的 API,如 java.net.HttpURLConnection 等。
2)RestTemplate(ClientHttpRequestFactory requestFactory)
3)RestTemplate(List<HttpMessageConverter<?>> messageConverters)

org.springframework.http.client.ClientHttpRequestFactory 接口的实现类给出底层实现的第三方 HTTP 客户端软件。

org.springframework.http.converter.HttpMessageConverter 接口的实现对象能够在HTTP消息与Java POJO之间进行数据转换。

5、 RestTemplate 类能够以 rest 风格的方式,以 GET, POST, PUT, DELETE, HEAD, OPTIONS 等不同的方式向服务器发起HTTP 请求。

GET 请求的方法:

T getForObject(String url, Class<T> responseType, Object... uriVariables)
T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
T getForObject(URI url, Class<T> responseType)

POST 请求的方法:

T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables)
T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables)
T postForObject(URI url, @Nullable Object request, Class<T> responseType)

String url = "http://192.168.2.77:8080/system/getUnitAssetsAndEquipmentInfo";
JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance;
//使用 jackson API 封装成 json 格式数据进行发送
ObjectNode objectNode = jsonNodeFactory.objectNode();
ArrayNode arrayNode = objectNode.putArray("orgCodes");
arrayNode.add("20802");
arrayNode.add("17592");
arrayNode.add(UUID.randomUUID().toString());
String result = restTemplate.postForObject(url, objectNode, String.class);

//如果对方返回得也是 json 格式得数据,则同样使用 jackson API 将其反序列化为 json 对象
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(result);
System.out.println(jsonNode.toString());

PUT 请求的方法:

void put(String url, @Nullable Object request, Object... uriVariables)
void put(String url, @Nullable Object request, Map<String, ?> uriVariables)
void put(URI url, @Nullable Object request)

DELETE 请求的方法:

void delete(String url, Object... uriVariables)
void delete(String url, Map<String, ?> uriVariables)
void delete(URI url)

.....

微服务之间 http 请求

1、本文环境:Java jdk 8 + Spring boot 2.1.3 + spring cloud Greenwich.SR1 + spring 5.1.5。

2、这里新建三个应用 eurekaserver_changsha、eurekaclient_cat、eurekaclient_food。eurekaserver_changsha 做 eureka 服务端,eurekaclient_cat、eurekaclient_food 做 eureka 客户端往服务端注册。

3、通过浏览器访问 eurekaclient_cat,eurekaclient_cat 后台使用 RestTemplate 访问 eurekaclient_food,即实现微服务相互调用。

4、下一节将对 eurekaclient_food 微服务进行集群(多节点注册),在 eurekaclient_cat 中加上 Ribbon 以实现对 eurekaclient_food 请求时的负载均衡。

eurekaserver_changsha

eurekaserver_changsha 做 eureka 服务端,提供服务注册。eurekaserver_changsha 应用 pom.xml 核心内容如下:

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.1.3.RELEASE</version>
	<relativePath/> <!-- lookup parent from repository -->
</parent>
......
<properties>
	<java.version>1.8</java.version>
	<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>        
......
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
......

eurekaserver_changsha 应用 application.yml 内容如下:

server:
  port: 9393
eureka:
  server:
    enable-self-preservation: false #关闭自我保护机制
    eviction-interval-timer-in-ms: 60000 #驱逐计时器扫描失效服务间隔时间。(单位毫秒,默认 60*1000)
  instance:
    hostname: localhost
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      default-zone: http://${eureka.instance.hostname}:${server.port}/eureka/

eurekaserver_changsha 应用启动类内容如下:

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

}

eurekaclient_food

eurekaclient_food 做 eureka 客户端,为其他微服务提供服务。pom.xml 文件核心内容如下:

......
<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.1.3.RELEASE</version>
	<relativePath/> <!-- lookup parent from repository -->
</parent>
......
<properties>
	<java.version>1.8</java.version>
	<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
......
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
......

application.yml 内容如下:

server:
  port: 9395
spring:
  application:
    name: eureka-client-food  #微服务名称
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9393/eureka/ #eureka 服务器地址
  instance:
    prefer-ip-address: true # IP 地址代替主机名注册
    instance-id: changSha-food # 微服务实例id名称

eureka 客户端在应用启动类上可以不用加 @EnableEurekaClient 注解,所以默认即可,下面新建一个 控制层 代码提供 http 访问。

package wmx.com.eurekaclient_food.controller;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 菜谱
 * @author wangmaoxiong
 */
@RestController
public class Cuisine {
    @Value("${server.port}")
    private Integer server_port;
    /**
     * 获取湘菜菜谱数据。访问地址:http://localhost:9395/getHunanCuisine
     * @return 
     */
    @GetMapping("getHunanCuisine")
    public String getHunanCuisine() {
        JsonNodeFactory nodeFactory = JsonNodeFactory.instance;
        //为了后面多个节点集群,负载均衡访问的时候,能分得清请求的到底是谁,这里特意返回应用的端口号
        ArrayNode arrayNode = nodeFactory.arrayNode().add(server_port)
                .add("辣椒炒肉").add("剁椒鱼头").add("蚂蚁上树").add("麻婆豆腐");
        //返回 json数组: ["辣椒炒肉","剁椒鱼头","蚂蚁上树","麻婆豆腐"]
        return arrayNode.toString();
    }
}

eurekaclient_cat

eurekaclient_cat 做 eureka 客户端,pom.xml 文件内容与上面 eurekaclient_food 除了名称不同,其余的组件、版本完全一样,这里不再累述。application.yml 内容如下:

server:
  port: 9394

spring:
  application:
    name: eureka-client-cat  #微服务名称

eureka:
  client:
    service-url:
      defaultZone: http://localhost:9393/eureka/ #eureka 服务器地址
  instance:
    prefer-ip-address: true # IP 地址代替主机名注册
    instance-id: changSha-cat # 微服务实例id名称

启动类内容如下:

package wmx.com.eurekaclient_cat;

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

//Eureka 客户端在启动类上可以不加 @EnableEurekaClient 注解
@SpringBootApplication
public class EurekaclientCatApplication {

    /**
     * @SpringBootApplication 注解自己依赖了 @Configuration 注解,所以可以直接在启动类上使用 @Bean 注解
     * 当然也可以新建一个配置类(@Configuration) 专门管理需要创建的实例
     * @Bean 作用就是使用 DI 往 spring 容器中传教实例,以后可以直接使用 @Resource 取值使用。
     */
    @Bean
    public RestTemplate restTemplate() {
        //RestTemplate 有3个构造器,这里使用无参构造器,底层使用 jdk 原生的 java.net 下 api 进行 http 操作
        return new RestTemplate();
    }

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

}

新建一个提供 http 请求的控制层:

package wmx.com.eurekaclient_cat.controller;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.io.IOException;

@RestController
public class SystemController {

    //获取容器中创建好的 RestTemplate 实例
    @Resource
    private RestTemplate restTemplate;

    //通过 id 获取猫咪信息。这里只是简单的模拟,并不是操作数据库
    //请求地址:http://localhost:9394/getCatById?id=110
    @GetMapping("getCatById")
    public String getCatById(String id) throws IOException {
        //猫咪的基本信息,这里直接设置
        JsonNodeFactory nodeFactory = JsonNodeFactory.instance;
        ObjectNode objectNode = nodeFactory.objectNode();
        objectNode.put("id", id);
        objectNode.put("color", "white");
        objectNode.put("age", 0.2);

        //猫咪的食谱/菜谱信息则调用 eurekaclient_food 微服务进行获取
        String foodMenu = restTemplate.getForObject("http://localhost:9395/getHunanCuisine", String.class);
        if (!StringUtils.isEmpty(foodMenu)) {
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode jsonNode = objectMapper.readTree(foodMenu);//先将 json 字符串专户 json 节点对象
            objectNode.putPOJO("menu", jsonNode);//对象节点插入子节点
        }
        return objectNode.toString();
    }
}
微服务的请求地址暂时写死没有关系,下一节使用 Ribbon 的时候,会用微服务名称来代替,即把 url 中的 localhost:9395 使用微服务名 eurekaclient_food 代替即可。

访问测试

顺序启动 eurekaserver_changsha、eurekaclient_food、eurekaclient_cat。

http://localhost:9393/ :eureka 服务端可以看到所有注册的微服务节点。

http://localhost:9395/getHunanCuisine :eurekaclient_food 获取菜谱数据。
http://localhost:9394/getCatById?id=110 :eurekaclient_cat 根据 id 获取猫咪信息,同时会访问 eurekaclient_food 微服务。如果能返回上面的菜谱的信息,则说明 RestTemplate 发起的 http 请求微服务成功。

netflix ribbon 负载均衡

1、ribbon 与 eureka 一样都是 netflix 下的子项目,spring cloud 对它们进行了集成。

2、ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,可以轻松地将面向服务的 REST 模版请求自动转换成客户端负载均衡的服务调用。

Spring Cloud Ribbon 虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API 网关的请求转发等内容,实际上都是通过 Ribbon 实现。

4、nginx 在服务端实现负载均衡,ribbon 在客户端提供负载均衡。即客户端程序使用 ribbon 后,就会自动对多个节点微服务进行负载均衡。

5、负载均衡有好几种实现策略,常见的有:随机 (Random)、轮询 (RoundRobin)、一致性哈希(ConsistentHash)、哈希 (Hash)、加权(Weighted)。默认是轮询。官网地址:

https://github.com/Netflix/ribbon

https://spring.io/projects/spring-cloud-netflix

https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.1.0.RELEASE/single/spring-cloud-netflix.html#spring-cloud-ribbon

6、先将 eurakeclient_food 应用打包,然后使用命令行启动多个实例。

使用下面的命令启动2个实例,动态修改应用端口以及实例名称。

java -jar eurekaclient_food-0.0.1-SNAPSHOT.jar --server.port=9396 --eureka.instance.instance-id=changSha-food-9396
java -jar eurekaclient_food-0.0.1-SNAPSHOT.jar --server.port=9397 --eureka.instance.instance-id=changSha-food-9397

如上所示此时 Eureka 服务端可以看到微服务 EUREKA-CLIENTFOOD 下面有3个节点,带端口的是命令行启动的,最后一个是 IDEA 直接运行的。

ribbon 基本使用

1、现在开始修改 eurekaclient_cat 微服务代码,让它能实现负载均衡的请求 eurekaclient_food 下的多个节点,使用 ribbon 非常简单。

2、Ribbon 是客户端实现的负载均衡,也就是谁发起请求,谁自己实现负载均衡,所以 eurekaclient_cat 向 eurekaclient_food 发起请求,则现在应该修改 eurekaclient_cat 应用。显然第一步应该引入 ribbon 组件,但是 eureka 组件默认已经依赖了 ribbon 组件,所以只要引入了 eureka 就已经引入了 ribbon。

3、修改 eurekaclient_cat 创建 RestTemplate 实例的代码,加上 @LoadBalanced 使 RestTemplate 具有负载均衡的能力。

    /**
     * @SpringBootApplication 注解自己依赖了 @Configuration 注解,所以可以直接在启动类上使用 @Bean 注解
     * 当然也可以新建一个配置类(@Configuration) 专门管理需要创建的实例
     * @Bean 作用就是使用 DI 往 spring 容器中传教实例,以后可以直接使用 @Resource 取值使用。
     * <p>
     * LoadBalanced:英文就是负载均衡的意思,@LoadBalanced 注解表示 RestTemplate 具有负载均衡的能力。
     * 意味着 RestTemplate 每次发送 http 请求时,都会根据 ribbon 的负载均衡机制向微服务下的某台节点服务器发送请求
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        //RestTemplate 有3个构造器,这里使用无参构造器,底层使用 jdk 原生的 java.net 下 api 进行 http 操作
        return new RestTemplate();
    }

4、然后修改 RestTemplate 发起 http 请求的代码,将原来写死的 ip与端口换成微服务的名称,本文是为了演示简单,微服务名称直接写死了,实际开发中可以从配置文件中获取:

    //通过 id 获取猫咪信息。这里只是简单的模拟,并不是操作数据库
    //请求地址:http://localhost:9394/getCatById?id=110
    @GetMapping("getCatById")
    public String getCatById(String id) throws IOException {
        //猫咪的基本信息,这里直接设置
        JsonNodeFactory nodeFactory = JsonNodeFactory.instance;
        ObjectNode objectNode = nodeFactory.objectNode();
        objectNode.put("id", id);
        objectNode.put("color", "white");
        objectNode.put("age", 0.2);

        //猫咪的食谱/菜谱信息调用 eurekaclient_food 微服务进行获取。
        //未使用负载均衡时的地址:http://localhost:9395/getHunanCuisine。使用负载均衡后,其中的 ip:port 必须使用微服务名称代替
        //EUREKA-CLIENT-FOOD 是在注册中心注册好的微服务名称(不是节点名称),也就是微服务配置文件中使用 spring.application.name 配置的名称
        String foodMenu = restTemplate.getForObject("http://EUREKA-CLIENT-FOOD/getHunanCuisine", String.class);
        if (!StringUtils.isEmpty(foodMenu)) {
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode jsonNode = objectMapper.readTree(foodMenu);//先将 json 字符串专户 json 节点对象
            objectNode.putPOJO("menu", jsonNode);//对象节点插入子节点
        }
        return objectNode.toString();
    }

5、代码很简单,修改两处就好了,接下来访问测试:

可以看出 ribbon 默认采用的轮询的策略,即对集群的服务轮流进行访问。

注意:具有负载均衡后的 RestTemplate 请求时只能使用微服务名称,无法再直接使用 ip:port 的形式,如果应用中还需要直接使用 ip 请求,则可以再提供一个非负载均衡的 RestTemplate 实例即可,使用时根据方法名注入。

    @Bean
    RestTemplate restTemplateNotBalance() {
        return new RestTemplate();
    }

更多负载均衡策略可以参考《Ribbon 负载均衡策略 与 脱离 Eureka 使用、LoadBalancerClient

演示源码 github 地址:https://github.com/wangmaoxiong/ribbon_study

发布了458 篇原创文章 · 获赞 884 · 访问量 92万+

猜你喜欢

转载自blog.csdn.net/wangmx1993328/article/details/94129475
今日推荐