Netflix Ribbon 简单实例

本章节将通过一个简单实例介绍怎样使用 Netflix Ribbon 实现负载均衡调用目标服务。

假如我们创建一个用户服务,该服务将分别运行在本机的 7001、7002 和 7003 三个端口。然后使用 Netflix Ribbon 实现简单的负载均衡调用。注意:这里没有将服务注册到 Eureka 服务端,也不用 Spring Cloud 相关的技术,仅仅是一个简单的 Java 项目。如下图:

上图中,我们将 user 服务分别运行在 7001、7002 和 7003 端口,然后客户端使用 Netflix Ribbon 进行负载均衡调用 user 服务。下面将介绍怎样实现上面实例:

提供 Eureka 服务

Eureka 服务启动类(点击查看完整的 pom.xml 依赖信息):

package com.hxstrive.springcloud.ribbon_eureka_server;

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

@SpringBootApplication
@EnableEurekaServer
public class RibbonEurekaServerApplication {

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

}

配置 application.yml 内容如下:

# 服务端口
server:
  port: 8077

# 服务名称
spring:
  application:
    name: eureka-server

# 服务地址
eureka:
  instance:
    hostname: localhost
  client:
    # 不向注册中心注册自己
    register-with-eureka: false
    # 取消检索服务
    fetch-registry: false
    # 启动独立模式的Eureka server
    # 关闭客户端行为,这样它就不会不断尝试并无法到达它的对等端
    # defaultZone 默认使用 http://localhost:8761/eureka/ 地址
    # 使 serviceUrl 指向与本地实例相同的主机
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  server:
    # 开启注册中心的保护机制,默认是开启
    enable-self-preservation: true
    # 设置保护机制的阈值,默认是0.85
    renewal-percent-threshold: 0.5

更多 Eureka 信息请参考“Netflix Eureka 教程”。

提供 User 服务

使用 Spring Boot 创建一个简单的 WEB 项目,并且提供一个 REST 接口,获取当前日期信息(点击查看完整的 pom.xml 依赖信息)。代码如下:

package com.hxstrive.springcloud.ribbon_service_users;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;

@RestController
@SpringBootApplication
// 开启 Eureka 客户端
@EnableEurekaClient
public class RibbonServiceUsersApplication {

    @Value("${appName:server}")
    private String appName;

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

    @GetMapping("/info")
    public String getInfo() {
        return "From " + appName + " - " + new Date();
    }

}

上面代码中,getInfo() 方法将返回当前日期,前面的 appName 是从我们执行“java -jar”命令时提供的,用于区分启动多个User服务时,说明返回数据来自哪个服务。项目编译成功后,通过 maven package 进行打包,然后执行如下命令:

# 服务1
java -jar ribbon_service_user-0.0.1-SNAPSHOT.jar --appName=user1 --server.port=7001 --eureka.instance.instance-id=user1

# 服务2
java -jar ribbon_service_user-0.0.1-SNAPSHOT.jar --appName=user2 --server.port=7002 --eureka.instance.instance-id=user2

# 服务3
java -jar ribbon_service_user-0.0.1-SNAPSHOT.jar --appName=user3 --server.port=7003 --eureka.instance.instance-id=user3

上面命令将启动三个服务。我们可以使用 http://localhost:7001/info  、http://localhost:7002/info  和  http://localhost:7003/info 三个地址分别调用接口。

注意,除了使用上述的脚本方式启动多个服务外,也可以使用 IDEA 启动多个服务,如下图:

(1)点击 IDEA 运行示例,显示下拉菜单,选择“Edit Configurations”,打开“Run/Debug Configurations”。如下图:

image.png

(2)在“Run/Debug Configurations”页面中复制多个示例,如下图中的“RibbonServiceUserApplication-7001”、“RibbonServiceUserApplication-7002”和“RibbonServiceUserApplication-7003”,并且修改对应的程序参数,如下图:

image.png

(3)启动运行示例,效果如下图:

image.png

实现客户端

使用 IDEA 创建一个简单的 Maven Java 项目。

(1)向项目中添加如下 Maven 依赖(点击查看完整的 pom.xml 依赖信息)

<dependency>
    <groupId>com.netflix.ribbon</groupId>
    <artifactId>ribbon</artifactId>
    <version>2.2.5</version>
</dependency>
<dependency>
    <groupId>com.netflix.ribbon</groupId>
    <artifactId>ribbon-core</artifactId>
    <version>2.2.5</version>
</dependency>
<dependency>
    <groupId>com.netflix.ribbon</groupId>
    <artifactId>ribbon-httpclient</artifactId>
    <version>2.2.5</version>
</dependency>
<dependency>
    <groupId>com.netflix.ribbon</groupId>
    <artifactId>ribbon-loadbalancer</artifactId>
    <version>2.2.5</version>
</dependency>
<dependency>
    <groupId>com.netflix.archaius</groupId>
    <artifactId>archaius-core</artifactId>
    <version>0.7.4</version>
</dependency>
<dependency>
    <groupId>commons-configuration</groupId>
    <artifactId>commons-configuration</artifactId>
    <version>1.8</version>
</dependency>
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>16.0.1</version>
</dependency>

(2)添加 application.properties 配置文件,内容如下:

# Max number of retries
user.ribbon.MaxAutoRetries=1

# Max number of next servers to retry (excluding the first server)
user.ribbon.MaxAutoRetriesNextServer=1

# Whether all operations can be retried for this client
user.ribbon.OkToRetryOnAllOperations=true

# Interval to refresh the server list from the source
user.ribbon.ServerListRefreshInterval=2000

# Connect timeout used by Apache HttpClient
user.ribbon.ConnectTimeout=3000

# Read timeout used by Apache HttpClient
user.ribbon.ReadTimeout=3000

# Initial list of servers, can be changed via Archaius dynamic property at runtime
user.ribbon.listOfServers=localhost:7001,localhost:7002,localhost:7003
user.ribbon.EnablePrimeConnections=true

注意:上面配置中 user.ribbon.listOfServers 配置我们刚刚上面启动的服务地址,分别为 7001、7002 和 7003 端口。而在每个配置项前面拥有前缀“user”,该前缀为客户端名称,后续在 ClientFactory.getNamedClient("user") 代码中使用,用来获取 RestClient 对象。

(3)新增 Demo1.java 文件,代码如下:

package com.hxstrive.springcloud.ribbon_demo1;

import com.netflix.client.ClientFactory;
import com.netflix.client.http.HttpRequest;
import com.netflix.client.http.HttpResponse;
import com.netflix.config.ConfigurationManager;
import com.netflix.niws.client.http.RestClient;

public class Demo1 {

    public static void main(String[] args) throws Exception{
        // 1.加载配置信息
        ConfigurationManager.loadPropertiesFromResources("application.properties");
        System.out.println(ConfigurationManager.getConfigInstance().getProperty("user.ribbon.listOfServers"));

        // 2.返回名称为 user 的 RestClient 客户端
        // 注意:这里的名称是在 application.properties 文件中配置 ribbon 时的前缀
        //       user.ribbon.listOfServers=localhost:7001,localhost:7002,localhost:7003
        //       上面配置中的 user 就是客户端名称
        RestClient client = (RestClient) ClientFactory.getNamedClient("user");

        // 3.构建指定 URL 的 HTTP 请求
        HttpRequest request = HttpRequest.newBuilder().uri("/info").build();
        for (int i = 0; i < 10; i++)  {
            // 4.使用负载均衡算法发起 HTTP 请求
            HttpResponse response = client.executeWithLoadBalancer(request);
            // 打印调用状态和结果
            System.out.println("Status code for " + response.getRequestedURI() + "  status:"
                    + response.getStatus() + " entity: " + response.getEntity(String.class));
        }
    }

}

运行上面代码,输出结果如下:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[localhost:7001, localhost:7002, localhost:7003]
Status code for http://localhost:7002/info  status:200 entity: From user2 - Fri Mar 26 18:00:09 CST 2021
Status code for http://localhost:7003/info  status:200 entity: From user3 - Fri Mar 26 18:00:09 CST 2021
Status code for http://localhost:7001/info  status:200 entity: From user1 - Fri Mar 26 18:00:09 CST 2021
Status code for http://localhost:7002/info  status:200 entity: From user2 - Fri Mar 26 18:00:09 CST 2021
Status code for http://localhost:7003/info  status:200 entity: From user3 - Fri Mar 26 18:00:09 CST 2021
Status code for http://localhost:7001/info  status:200 entity: From user1 - Fri Mar 26 18:00:09 CST 2021
Status code for http://localhost:7002/info  status:200 entity: From user2 - Fri Mar 26 18:00:09 CST 2021
Status code for http://localhost:7003/info  status:200 entity: From user3 - Fri Mar 26 18:00:09 CST 2021
Status code for http://localhost:7001/info  status:200 entity: From user1 - Fri Mar 26 18:00:09 CST 2021
Status code for http://localhost:7002/info  status:200 entity: From user2 - Fri Mar 26 18:00:09 CST 2021

仔细观察可知,客户端使用了轮询的方式调用服务列表。即调用 7002、7003 和 7001,然后再次调用 7002、7003……

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号