IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> Spring Cloud Netflix-Ribbon基本使用及其原理 -> 正文阅读

[大数据]Spring Cloud Netflix-Ribbon基本使用及其原理

一、负载均衡

在分布式的时代,服务必定是多个实例的,系统再进行服务间通信时,必定需要根据当前服务实例集合,选择一个实例进行通信。而已负载均衡(Load Balance)就是基于这个产生的。

负载均衡其意思就是分摊到多个操作单元上分别执行。
在这里插入图片描述

二、Ribbon

1. 简介

Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松的将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。

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

2. 基本使用

本实例基于独立的SpringCloud Ribbon实现了一个简单的客户端负载均衡案例。
在这里插入图片描述

  • finpc为业务聚合服务。
  • order为订单服务。
  • sys为系统服务。
  • finpc、order、sys都要引入service-api依赖

2.1 引入 pom.xml 依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.4.3</version>
</dependency>
<!--spring cloud ribbon-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    <version>2.2.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.1</version>
</dependency>

2.2 服务提供方

@RestController
@RequestMapping("/user")
public class UserService implements IUserService {
    private static final Map<Long, UserBean> USER_BEAN_MAP = new ConcurrentHashMap<>();

    @Value("${server.port}")
    private String port;

    static {
        UserBean userBean = new UserBean();
        userBean.setId(111L);
        userBean.setName("test");
        userBean.setSex("男生");
        USER_BEAN_MAP.put(111L, userBean);
    }

    @Override
    @GetMapping("/get")
    public UserBean get(Long id) {
        System.out.println("----------------port=" + port);
        return UserService.USER_BEAN_MAP.get(id);
    }
}

2.3 服务调用方

服务调用方就是进行负载均衡的一分,通过对服务提供方的服务列表,利用 Ribbon 进行负载调用服务。

@Configuration
public class RibbonConfiguration {

    /**
     * 实例化ribbon使用的RestTemplate
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        /*使用OkHttp进行服务通信*/
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }

    /**
     * 默认 RestTemplate
     * @return
     */
    @Bean
    public RestTemplate defaultRestTemplate() {
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }
}

@RestController
@RequestMapping("/finpc")
public class FinpcController {

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @Autowired
    private RestTemplate restTemplate;

    @Resource(name = "defaultRestTemplate")
    private RestTemplate defaultRestTemplate;

    @GetMapping("/getUser/{id}")
    public UserBean getUser(@PathVariable() Long id) {
        UserBean forObject = restTemplate.getForObject("http://lizq-sys/user/get?id={id}", UserBean.class, id);
        return forObject;
    }

    @GetMapping("/getUser1/{id}")
    public UserBean getUser1(@PathVariable() Long id) {
    	// 直接通过Ribbon客户端进行服务选择,然后进行Http通信
        ServiceInstance instance = loadBalancerClient.choose("lizq-sys");
        URI storesUri = URI.create(String.format("http://%s:%s/user/get?id=%s", instance.getHost(), instance.getPort(), id));
        UserBean forObject = defaultRestTemplate.getForObject(storesUri, UserBean.class);
        return forObject;
    }
}

2.4 配置文件application.yaml

server:
  port: 8080
spring:
  application:
    name: lizq-finpc
# lizq-sys为需要负载的服务名
lizq-sys:
  ribbon:
    listOfServers: http://localhost:8888,http://localhost:8889

2.5 测试

  • 启动两个lizq-sys服务,端口分别为8888、8889
  • 启动服务调用方
  • 发起请求:http://localhost:8080/finpc/getUser/111、http://localhost:8080/finpc/getUser1/111

上面就是一个简单使用 Ribbon 的例子,Ribbon 作为一个独立的组件,可以不依赖与任何第三方进行使用。

3. 负载均衡算法

Ribbon内置了7种负载均衡算法,并且保留了扩展,用户可通过实现 IRule 接口,实现自定义负载均衡算法。

3.1 内置7种负载均衡算法

在这里插入图片描述

  1. BestAviableRule
    跳过熔断的Server,在剩下的Server中选择并发请求最低的Server。
  2. ZoneAvoidanceRule
    随机选择一个server。
  3. AvailabilityFilteringRule
    剔除因为连续链接、读失败或链接超过最大限制导致熔断的Server,在剩下读Server中进行轮询。
  4. RoundRobinRule
    roundRobin方式轮询选择server,默认
  5. RandomRule
    随机选择一个server。
  6. RetryRule
    可重试的策略,可以对其他策略进行重试。
  7. ResponseTimeWeightedRule
    根据响应时间加权,响应时间越短权重越大,被选中的可能性越高。

3.2 自定义负载均衡算法

package com.netflix.loadbalancer;

public interface IRule {
    Server choose(Object key);
    void setLoadBalancer(ILoadBalancer loadBalancer);
    ILoadBalancer getLoadBalancer();
}

IRule 是负载均衡策略的抽象,ILoadBalancer 通过调用 IRulechoose(Object key) 方法返回 Server。

IPHash 算法: 根据调用者的ip hash后进行服务选择。

在 application.yaml 中添加配置:

# lizq-sys为需要负载的服务名
lizq-sys:
  ribbon:
    listOfServers: http://localhost:8888,http://localhost:8889
    NFLoadBalancerRuleClassName: com.lizq.finpc.rule.IpHashRule

com.lizq.finpc.rule.IpHashRule 实现

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.Server;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.List;

public class IpHashRule extends AbstractLoadBalancerRule {
    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

    @Override
    public Server choose(Object key) {
        if (getLoadBalancer() == null) {
            return null;
        }
        Server server = null;
        while (server == null) {
            // 表示可用的服务列表.(默认情况下单纯只用Ribbon时,不会对目标服务做心跳检测)
            List<Server> upList = getLoadBalancer().getReachableServers();
            // List<Server> allList = getLoadBalancer().getAllServers();
            int serverCount = upList.size();
            if (CollectionUtils.isEmpty(upList)) {
                return null;
            }
            int index = ipAddressHash(serverCount);
            server = upList.get(index);
        }
        return server;
    }

    /**
     * @param serverCount
     * @return
     */
    private int ipAddressHash(int serverCount) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String remoteAddr = requestAttributes.getRequest().getRemoteAddr();
        int code = Math.abs(remoteAddr.hashCode());
        return code % serverCount;
    }
}

三、Ribbon实现原理

1. Ribbon底层原理猜想

在这里插入图片描述

2. Ribbon底层原理与源码分析

Ribbon 实现的关键点是利用了 RestTemplate 的拦截器机制,在拦截器中实现 Ribbon 的负载均衡。负载均衡的基本实现就是利用applicationName 从配置文件或服务注册中心获取可用的服务地址列表,然后通过一定算法负载,返回服务地址后进行 Http 调用。

RestTemplate 拦截器机制
RestTemplate 中有一个属性是 List<ClientHttpRequestInterceptor> interceptors,如果 interceptors 里面的拦截器数据不为空,在RestTemplate 进行 Http 请求时,这个请求就会被拦截器拦截。

拦截器需要实现 ClientHttpRequestInterceptor 接口

package org.springframework.http.client;

import java.io.IOException;
import org.springframework.http.HttpRequest;

@FunctionalInterface
public interface ClientHttpRequestInterceptor {
    ClientHttpResponse intercept(HttpRequest httpRequest, byte[] body, ClientHttpRequestExecution execution) throws IOException;
}

Ribbon 中的 RestTemplate 拦截器:LoadBalancerInterceptor

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    private LoadBalancerClient loadBalancer;
    private LoadBalancerRequestFactory requestFactory;

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
        this.loadBalancer = loadBalancer;
        this.requestFactory = requestFactory;
    }

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
        this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
    }

    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
        URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        // 通过LoadBalance算法选择serviceName对应的所有的可用服务
        return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
    }
}

/**
 * 拦截请求执行
 */
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        return this.execute(serviceId, (LoadBalancerRequest)request, (Object)null);
    }

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
	// 获取当前serviceId对应的LoadBalance
    ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
	// 通过loadBalance选择 Server
    Server server = this.getServer(loadBalancer, hint);
    if (server == null) {
        throw new IllegalStateException("No instances available for " + serviceId);
    } else {
        RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
        // 执行请求
        return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
    }
}

/**
 * 通过loadBalance选择 Server
 */
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
	// 执行LoadBalance中的choose(Object key) 方法,返回对应的Server
    return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
}

定义注入器、拦截器

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"})
static class LoadBalancerInterceptorConfig {
    LoadBalancerInterceptorConfig() {
    }

    @Bean
    public LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
        return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
    }

    @Bean
    @ConditionalOnMissingBean
    public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
    	// 定义注入器,用来将拦截器注入到RestTemplate中
        return (restTemplate) -> {
            List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
            list.add(loadBalancerInterceptor);
            restTemplate.setInterceptors(list);
        };
    }
}

将 Ribbon 拦截器注入到 RestTemplate

@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

	/**
	 * 获取所有的 @LoadBalanced 标注的RestTemplate 对象
	 */
   @LoadBalanced
   @Autowired(required = false)
   private List<RestTemplate> restTemplates = Collections.emptyList();

   @Bean
   public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
         final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
       // 遍历context中的注入器,调用注入方法。
      return () -> restTemplateCustomizers.ifAvailable(customizers -> {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        });
   }

   ........
}

遍历 context 中的注入器(RestTemplateCustomizer),调用注入方法,为目标 RestTemplate 注入拦截器。

还有关键的一点是:需要注入拦截器的目标 RestTemplates 到底是哪一些?因为RestTemplate实例在context中可能存在多个,不可能所有的都注入拦截器,这里就是 @LoadBalanced 注解发挥作用的时候了。

@LoadBalanced 注解

这个注解是 Spring Cloud 实现的,不是 Ribbon 中的,它的作用是在依赖注入时,只注入被 @LoadBalanced 修饰的实例。

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

那么 @LoadBalanced 是如何实现这个功能的呢?其实都是 Spring 的原生操作。@LoadBalanced 继承了 @Qualifier 注解,@Qualifier注解用来指定想要依赖某些特征的实例,这里的注解就是类似的实现,RestTemplates 通过 @Autowired 注入,同时被@LoadBalanced 修饰,所以只会注入 @LoadBalanced 修饰的 RestTemplate,也就是我们的目标 RestTemplate。

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-01-03 16:10:38  更:2022-01-03 16:12:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 13:37:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码