微服务技术系列教程(21) - SpringCloud- 负载均衡器Ribbon

本文涉及的产品
应用型负载均衡 ALB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
简介: 微服务技术系列教程(21) - SpringCloud- 负载均衡器Ribbon

01 引言

在SpringCloud中Ribbon负载均衡客户端,会从eureka注册中心服务器端上获取服务注册信息列表,缓存到本地,然后在本地实现轮训负载均衡策略

那么Ribbon和Nginx有什么区别呢?

  • Nginx是客户端所有请求统一交给Nginx,由nginx进行实现负载均衡请求转发,属于服务器端负载均衡(服务器端转发)。
  • 在客户端转发:Ribbon是从eureka注册中心服务器端上获取服务注册信息列表,缓存到本地,让后在本地实现轮训负载均衡策略(客户端转发)。
  • Nginx适合于服务器端实现负载均衡 比如Tomcat(服务器端负载均衡)。
  • Ribbon适合与在微服务中RPC远程调用实现本地服务负载均衡,比如Dubbo、SpringCloud中都是采用本地负载均衡(客户端负载均衡)。

Ribbon流程图:

02 DiscoveryClient负载本地负载均衡

1.搭建Eureka-Service-A1(服务提供者)、Eureka-Service-A2(服务提供者)、Eureka-Service-B(服务消费者)和Eureka-Server(注册中心),环境搭建参考:《 SpringCloud- 服务治理Eureka(搭建注册中心)》

2.Eureka-Service-B(服务消费者)负载均衡代码:

@RestController
public class ServiceBController {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private DiscoveryClient discoveryClient;
    private int requestCount = 1;
    @RequestMapping("/discoveryClient")
    public String discoveryClient() {
        String serviceUrl = getServiceUrl() + "/getMember";
        return "请求地址为 -> " + serviceUrl;
    }
    @RequestMapping("/getServiceUrl")
    private String getServiceUrl() {
        List<ServiceInstance> instances = discoveryClient.getInstances("app-service-a");
        if (instances == null || instances.size() == 0) {
            return null;
        }
        int size = instances.size();
        int index = requestCount % size;
        requestCount++;
        return instances.get(index).getUri().toString();
    }
}

2.启动Eureka注册中心服务(http://localhost:8100/)、服务器消费者(http://localhost:8000/http://localhost:8001/)、服务器提供者(http://localhost:8000/)。

3.登录Eureka注册中心:http://localhost:8100/,可以看到都注册上了。

4.服务消费者消费,浏览器输入:http://localhost:8002/discoveryClient

再次刷新:

可以看出,Eureka消费者客户端通过轮询的方式来实现本地负载均衡。

03 RestTemplate

RestTemplate是Spring用于同步client端的核心类,简化了与http服务的通信,并满足RestFul原则,程序代码可以给它提供URL,并提取结果。能大幅简化了提交表单数据的难度,并且附带了自动转换JSON数据的功能。(说白了,库室HttpClient的封装,像Apache HttpComponents、Netty和OkHttp。)

该类的入口主要是根据HTTP的六个方法制定:

Http Method RestTemplate Methods
GET getForObject、getForEntify
DELETE delete
POST postForLocation、postForObject
PUT put
HEAD headForHeaders
OPTIONS optionsForAllow
ANY exchange、execute

此外,exchange和excute可以通用上述方法。

在内部,RestTemplate默认使用HttpMessageConverter实例将HTTP消息转换成POJO或者从POJO转换成HTTP消息。默认情况下会注册主mime类型的转换器,但也可以通过setMessageConverters注册其他的转换器。

HttpMessageConverter.java源码:

public interface HttpMessageConverter<T> {
        //指示此转换器是否可以读取给定的类。
    boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
        //指示此转换器是否可以写给定的类。
    boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
        //返回List<MediaType>
    List<MediaType> getSupportedMediaTypes();
        //读取一个inputMessage
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException;
        //往output message写一个Object
    void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException;
}

在内部,RestTemplate默认使用SimpleClientHttpRequestFactory和DefaultResponseErrorHandler来分别处理HTTP的创建和错误,但也可以通过setRequestFactory和setErrorHandler来覆盖。


演示实例前,先定义POJO:

public class Notice {
    private int status;
    private Object msg;
    private List<DataBean> data;
}
public  class DataBean {
  private int noticeId;
  private String noticeTitle;
  private Object noticeImg;
  private long noticeCreateTime;
  private long noticeUpdateTime;
  private String noticeContent;
}

3.1 Get请求

getForEntity:getForEntity方法的返回值是一个ResponseEntity<T>ResponseEntity<T>是Spring对HTTP请求响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体等。

@Test
public void rtGetEntity(){
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<Notice> entity = restTemplate.getForEntity("http://fantj.top/notice/list/1/5"
                , Notice.class);
        HttpStatus statusCode = entity.getStatusCode();
        System.out.println("statusCode.is2xxSuccessful()"+statusCode.is2xxSuccessful());
        Notice body = entity.getBody();
        System.out.println("entity.getBody()"+body);
        ResponseEntity.BodyBuilder status = ResponseEntity.status(statusCode);
        status.contentLength(100);
        status.body("我在这里添加一句话");
        ResponseEntity<Class<Notice>> body1 = status.body(Notice.class);
        Class<Notice> body2 = body1.getBody();
        System.out.println("body1.toString()"+body1.toString());
    }
statusCode.is2xxSuccessful()true
entity.getBody()Notice{status=200, msg=null, data=[DataBean{noticeId=21, noticeTitle='aaa', ...
body1.toString()<200 OK,class com.waylau.spring.cloud.weather.pojo.Notice,{Content-Length=[100]}>

getForObjet:getForObject函数实际上是对getForEntity函数的进一步封装,如果你只关注返回的消息体的内容,对其他信息都不关注,此时可以使用getForObject,举一个简单的例子,如下:

/**
* 带参数与不带参的get请求
*/
@Test
public void restTemplateGetTest(){
   RestTemplate restTemplate = new RestTemplate();
   Notice notice = restTemplate.getForObject("http://xxx.top/notice/list/1/5", Notice.class);
   //Notice notice = restTemplate.getForObject("http://fantj.top/notice/list/{1}/{2}" , Notice.class,1,5);
   System.out.println(notice);
}

3.2 Post请求

如果你只关注,返回的消息体,可以直接使用postForObject。用法和getForObject一致。

@Test
public void rtPostObject(){
    RestTemplate restTemplate = new RestTemplate();
    String url = "http://47.xxx.xxx.96/register/checkEmail";
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    MultiValueMap<String, String> map= new LinkedMultiValueMap<>();
    map.add("email", "xxx@qq.com");
    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
    ResponseEntity<String> response = restTemplate.postForEntity( url, request , String.class );
    System.out.println(response.getBody());
}

3.3 Put请求、Delete请求

跟上面大同小异,具体的看API。参考文章:《Springboot — 用更优雅的方式发HTTP请求(RestTemplate详解)》

最后,先看下代码:

@SpringBootApplication
@EnableEurekaClient
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
    @Bean
    //@LoadBalanced//@LoadBalanced就能让这个RestTemplate在请求时拥有客户端负载均衡的能力(增加@LoadBalanced,就不能使用127.0.0.1,只能使用应用名)
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

需要注意的是:增加@LoadBalanced,就不能使用本机ip例如(127.0.0.1、localhost),只能使用应用名。

04 源码分析

参考:《客户端负载均衡Ribbon之源码解析》

ServerList:可以响应客户端的特定服务的服务器列表。

ServerListFilter:可以动态获得的具有所需特征的候选服务器列表的过滤器。

ServerListUpdater:用于执行动态服务器列表更新。

Rule:负载均衡策略,用于确定从服务器列表返回哪个服务器。

Ping:客户端用于快速检查服务器当时是否处于活动状态。

LoadBalancer:负载均衡器,负责负载均衡调度的管理。

05 负载均衡器重试机制

这里的重试并不是报错以后的重试,而是负载均衡客户端发现远程请求实例不可到达后,去重试其他实例。

Ribbon重试机制配置:

名称 解析
ribbon.OkToRetryOnAllOperations false(是否所有操作都重试)
ribbon.MaxAutoRetriesNextServer 2(重试负载均衡其它的实例最大重试次数,不包括Server)
ribbon.MaxAutoRetries 1(同一台实例最大重试次数,不包括首次调用)
spring.cloud.loadbalancer.retry.enabled true(重试机制开关)

04 总结

代码已上传至Github,有兴趣的同学可以下载来看看:https://github.com/ylw-github/SpringCloud-Ribbon-Demo

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
27天前
|
Kubernetes Cloud Native 微服务
探索云原生技术:容器化与微服务架构的融合之旅
本文将带领读者深入了解云原生技术的核心概念,特别是容器化和微服务架构如何相辅相成,共同构建现代软件系统。我们将通过实际代码示例,探讨如何在云平台上部署和管理微服务,以及如何使用容器编排工具来自动化这一过程。文章旨在为开发者和技术决策者提供实用的指导,帮助他们在云原生时代中更好地设计、部署和维护应用。
|
2月前
|
运维 Kubernetes Cloud Native
云原生技术:容器化与微服务架构的完美结合
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术以其灵活性和高效性成为企业的新宠。本文将深入探讨云原生的核心概念,包括容器化技术和微服务架构,以及它们如何共同推动现代应用的发展。我们将通过实际代码示例,展示如何在Kubernetes集群上部署一个简单的微服务,揭示云原生技术的强大能力和未来潜力。
|
1月前
|
缓存 负载均衡 算法
深入理解后端服务的负载均衡技术
在现代网络服务架构中,高效的负载均衡策略对于保证应用性能和可靠性至关重要。本文将深入探讨后端服务中的负载均衡技术,包括其重要性、常见算法以及如何实现高效均衡。通过分析不同的负载均衡方法,我们旨在为开发者提供实用的指导,帮助他们优化自己的系统架构。
|
1月前
|
负载均衡 网络协议 算法
Docker容器环境中服务发现与负载均衡的技术与方法,涵盖环境变量、DNS、集中式服务发现系统等方式
本文探讨了Docker容器环境中服务发现与负载均衡的技术与方法,涵盖环境变量、DNS、集中式服务发现系统等方式,以及软件负载均衡器、云服务负载均衡、容器编排工具等实现手段,强调两者结合的重要性及面临挑战的应对措施。
74 3
|
2月前
|
Kubernetes Java 微服务
微服务上下线动态感知实现的技术解析
随着微服务架构的广泛应用,服务的动态管理和监控变得尤为重要。在微服务架构中,服务的上下线是一个常见的操作,如何实时感知这些变化,确保系统的稳定性和可靠性,成为了一个关键技术挑战。本文将深入探讨微服务上下线动态感知的实现方式,从技术基础、场景案例、解决思路和底层原理等多个维度进行阐述,并分别使用Java和Python进行演示介绍。
65 4
|
2月前
|
运维 Kubernetes Docker
深入理解容器化技术及其在微服务架构中的应用
深入理解容器化技术及其在微服务架构中的应用
65 1
|
2月前
|
监控 Java 微服务
从零构建微服务架构:一次深度技术探索之旅####
本文作为一篇深度技术分享,引领读者踏上自底向上搭建微服务架构的征途,旨在通过实战经验剖析,揭示微服务转型背后的技术挑战与解决方案。不同于常规摘要仅概述内容,本文摘要将直接以故事化手法,简述作者从单体应用困境出发,逐步迈向微服务化的心路历程,涵盖关键决策点、技术选型考量及实践收获,激发读者对微服务架构设计与实现的浓厚兴趣。 ####
|
2月前
|
Cloud Native 云计算 Docker
云原生技术的崛起:从容器化到微服务架构
云原生技术的崛起:从容器化到微服务架构
|
2月前
|
监控 API 微服务
后端技术演进:从单体架构到微服务的转变
随着互联网应用的快速增长和用户需求的不断演化,传统单体架构已难以满足现代软件开发的需求。本文深入探讨了后端技术在面对复杂系统挑战时的演进路径,重点分析了从单体架构向微服务架构转变的过程、原因及优势。通过对比分析,揭示了微服务架构如何提高系统的可扩展性、灵活性和维护效率,同时指出了实施微服务时面临的挑战和最佳实践。
58 7
|
2月前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
144 6